"""Roda o JavaScript do protótipo num DOM falso e exercita telas e ações, para pegar erros de execução."""
import json
import re
import sys

import quickjs

html = open(sys.argv[1], encoding='utf-8').read()
script = re.findall(r'<script>(.*?)</script>', html, re.S)[0]
fim = script.rfind('})();')
exporta = ('\nglobalThis.__t = { get estado() { return estado; }, ACOES, render, htmlMsg, htmlChat, responder, '
           'aprovarAtual, pularAtual, editarAtual, atual, pendentes, definirModo, K, livresAPartir, gradeDoDia, '
           'PESSOAS, grupoDe, pacientePorNome, filtrarPacientes, pendenciasDe, faltamDe, '
           'juntarPorPaciente, aplicarCota, prazoPrimeiraResposta, telefoneProblema, problemaTelefone, '
           'paresDuplicados, duplicadosDe, selecionarMissao, missaoDe, pendenciasCadastro, conferirPedido, '
           'entrarNaFila, situacaoDe, metaCadastro, visiveis, VAGA, COTA_AGENTES, '
           'lerMensagem, conferirPeca, vencimentosHomecare, estadoSerie, textoAprovado, faltamAprovar, sugestoesDe, textoMarcacao, '
           'textoConfirmacao, textoEnvio, anexoInfo, listaDeEspera, primeiraVez, VAGAS, N_SESSOES, TEXTOS_FIXOS, '
           'EXEMPLO_COLAR, cartao, convite, CONFERE, proximasDaSequencia, HOMECARE, PARCEIRAS, textoConviteParceira, '
           'textoAgradecerParceira, INSUMOS_BASE, KIT, kitDe, procsDe, diasDe, acabando, usoPorDia, htmlSaida, fimDoCiclo, '
           'textoForma, PRECOS, LEMBRETE_RELATO, LEMBRETE_SAIDA, TEXTO_MMP, htmlFicha };\n')
script = script[:fim] + exporta + script[fim:]

SHIM = r'''
var __DataReal = globalThis.Date;
var __AGORA = new __DataReal(2026, 9, 6, 9, 20, 0).getTime();
globalThis.Date = function Date(a, b, c, d, e, f, g) {
  if (!(this instanceof Date)) return new __DataReal(__AGORA).toString();
  if (arguments.length === 0) return new __DataReal(__AGORA);
  if (arguments.length === 1) return new __DataReal(a);
  return new __DataReal(a, b, c === undefined ? 1 : c, d || 0, e || 0, f || 0, g || 0);
};
globalThis.Date.now = function () { return __AGORA; };
globalThis.Date.UTC = __DataReal.UTC;
globalThis.Date.parse = __DataReal.parse;
globalThis.Date.prototype = __DataReal.prototype;
var __handlers = {};
var __timers = [];
function setTimeout(fn) { __timers.push(fn); return __timers.length; }
function clearTimeout() {}
function __flush() { var n = 0; while (__timers.length && n < 200) { __timers.shift()(); n++; } }
function ListaClasses() { this.s = new Set(); }
ListaClasses.prototype.add = function (c) { this.s.add(c); };
ListaClasses.prototype.remove = function (c) { this.s.delete(c); };
ListaClasses.prototype.toggle = function (c, f) { if (f === undefined) f = !this.s.has(c); if (f) this.s.add(c); else this.s.delete(c); return f; };
ListaClasses.prototype.contains = function (c) { return this.s.has(c); };
function El(id) { this.id = id; this.classList = new ListaClasses(); this.innerHTML = ''; this.textContent = ''; this.hidden = false; this.dataset = {}; this.style = {}; this.attrs = {}; this.scrollTop = 0; this.scrollHeight = 0; this.value = ''; }
Object.defineProperty(El.prototype, 'className', { get: function () { return Array.from(this.classList.s).join(' '); }, set: function (v) { this.classList.s = new Set(String(v).split(/\s+/).filter(Boolean)); } });
Object.defineProperty(El.prototype, 'lastElementChild', { get: function () { return new El('ultimo'); } });
El.prototype.setAttribute = function (k, v) { this.attrs[k] = String(v); };
El.prototype.getAttribute = function (k) { return k in this.attrs ? this.attrs[k] : null; };
El.prototype.removeAttribute = function (k) { delete this.attrs[k]; };
El.prototype.hasAttribute = function (k) { return k in this.attrs; };
El.prototype.querySelector = function () { return null; };
El.prototype.querySelectorAll = function () { return []; };
El.prototype.closest = function () { return null; };
El.prototype.focus = function () {};
El.prototype.scrollIntoView = function () {};
El.prototype.insertAdjacentHTML = function (pos, h) { this.innerHTML += h; };
El.prototype.getBoundingClientRect = function () { return { top: 0, left: 0, width: 0, height: 0 }; };
El.prototype.appendChild = function (c) { return c; };
El.prototype.remove = function () {};
El.prototype.setSelectionRange = function () {};
var __els = {};
function getEl(id) { if (!__els[id]) __els[id] = new El(id); return __els[id]; }
var CONHECIDOS = ['tela', 'horaTopo', 'horaIphone', 'painel', 'veu', 'toast', 'botaoTema', 'telaApp'];
['painel', 'veu', 'toast'].forEach(function (id) { getEl(id).hidden = true; });
var document = {
  documentElement: new El('html'),
  body: new El('body'),
  activeElement: null,
  querySelector: function (s) {
    var m = /^#([\w-]+)$/.exec(s);
    if (!m) return null;
    var id = m[1];
    if (CONHECIDOS.indexOf(id) >= 0) return getEl(id);
    var p = __els.painel;
    if (id === 'chatDigitando') {
      var cm = __els.chatMensagens;
      if (!cm || cm.innerHTML.indexOf('id="chatDigitando"') < 0) return null;
      var d = new El('chatDigitando');
      d.remove = function () { cm.innerHTML = cm.innerHTML.replace(/<div class="msg msg--ia" id="chatDigitando">[\s\S]*?<\/div><\/div>/, ''); };
      return d;
    }
    if (!p.hidden && p.innerHTML.indexOf('id="' + id + '"') >= 0) return getEl(id);
    if (__els.tela && __els.tela.innerHTML.indexOf('id="' + id + '"') >= 0) return getEl(id);
    return null;
  },
  querySelectorAll: function () { return []; },
  addEventListener: function (t, fn) { (__handlers[t] = __handlers[t] || []).push(fn); },
  contains: function () { return true; },
  createElement: function () { return new El('novo'); },
};
var window = globalThis;
window.matchMedia = function (q) { return { matches: /min-width: (1024|760)px|pointer: fine/.test(q) }; };
window.scrollTo = function () {};
window.addEventListener = function (t, fn) { (__handlers[t] = __handlers[t] || []).push(fn); };
var location = { _h: '', get hash() { return this._h; }, set hash(v) { v = String(v); this._h = v ? (v[0] === '#' ? v : '#' + v) : ''; (__handlers.hashchange || []).forEach(function (fn) { fn(); }); } };
var localStorage = { getItem: function () { return null; }, setItem: function () {} };
var navigator = { clipboard: { writeText: function () { return Promise.resolve(); } } };
'''

TESTES = r'''
var __out = [];
var T = globalThis.__t;
function run(nome, fn) { try { fn(); __flush(); __out.push('ok    ' + nome); } catch (err) { __out.push('FALHA ' + nome + ': ' + err + ' | ' + String((err && err.stack) || '').split('\n').slice(0, 3).join(' / ')); } }
function naoTem(trecho, onde) { var h = onde === undefined ? __els.tela.innerHTML : onde; if (h.indexOf(trecho) >= 0) throw new Error('não devia aparecer: ' + trecho); }
function tem(trecho, onde) { var h = onde === undefined ? __els.tela.innerHTML : onde; if (h.indexOf(trecho) < 0) throw new Error('não achei: ' + trecho); }
function painel() { return __els.painel.innerHTML; }
function ultimaMsg() { var c = T.estado.chat; return T.htmlMsg(c[c.length - 1], c.length - 1); }
function ctxCom(mapa) { var c = new El('ctx'); c.querySelector = function (s) { return mapa[s] || null; }; c.querySelectorAll = function (s) { return mapa['TODOS ' + s] || []; }; return c; }
function botao(acao, dados, ctx) {
  var e = new El('botao');
  e.dataset = Object.assign({ acao: acao }, dados || {});
  e.closest = function (s) { if (s === '[data-contexto]') return ctx || null; if (s === '.chat-mensagens' && dados && dados.__chat) return new El('caixa'); return null; };
  return e;
}
function opc(valor, texto) { var e = new El('opcao'); e.dataset = { valor: valor }; e.textContent = texto; return e; }
function pad(n) { return String(n).padStart(2, '0'); }
function chaveDe(d) { return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()); }
function daquiA(n) { var d = new Date(); d.setDate(d.getDate() + n); return d; }

run('abre no copiloto', function () { tem('Bom dia, Fer.'); tem('Peça ao copiloto'); tem('Horários livres'); tem('Recepção de hoje'); tem('Tarefas de hoje'); });
run('página inicial: visão geral do dia, sem tarefa ocupando a tela', function () {
  if (T.atual() !== null) throw new Error('já abriu uma tarefa sozinha');
  // Mudou de propósito na etapa 1: entram a pergunta sobre o Thiago, a resposta ao relato do Ricardo e o Fabrício (48 dias).
  tem('Tarefas de hoje'); tem('Urgente agora'); tem('title="7 pacientes">7<'); tem('Larissa'); tem('Paula'); tem('com 34 pacientes'); tem('0</strong><span>de 26 tarefas feitas'); // mudou de propósito na etapa 4, em 07/10 e de novo em 07/10, na rodada b: sem a pesquisa do Mateus, sem o convite e o Google do Jorge e sem o Google da Cristiane (o Google vai no dia da reavaliação)
  T.ACOES['abrir-tarefa'](botao('abrir-tarefa', { id: 'thiago' }));
  if (!T.atual() || T.atual().id !== 'thiago') throw new Error('não abriu a tarefa');
  tem('modal-tarefa'); tem('Ele veio?'); // no notebook, a tarefa abre na janela por cima da página
  T.ACOES['abrir-tarefa'](botao('abrir-tarefa', { id: 'thiago' }));
  if (T.atual() !== null) throw new Error('não fechou a tarefa');
  T.ACOES.selecionar(botao('selecionar', { id: 'confirmacoes', conf: '1-ana-0900-larissa' }));
  if (T.atual().id !== 'confirmacoes') throw new Error('não abriu as confirmações');
  tem('conf-linha--foco');
  T.ACOES.filtrar(botao('filtrar', { atrib: 'homecare' }));
  T.ACOES.selecionar(botao('selecionar', { id: 'thiago' }));
  if (T.estado.filtro !== '' || T.atual().id !== 'thiago') throw new Error('não saiu do filtro ao escolher outra pessoa');
  T.ACOES.selecionar(botao('selecionar', { id: 'juliana' }));
});
run('Luiz, 09/10 · Copiloto no notebook: os números do dia, seis grupos com as confirmações à parte e a tela de cada grupo', function () {
  location.hash = 'copiloto'; T.ACOES['abrir-grupo'](botao('abrir-grupo', { gid: '' }));
  ['urgentes', 'confirmacoes', 'agenda', 'recepcao', 'acompanhamento', 'administrativo'].forEach(function (g) { tem('data-gid="' + g + '"'); });
  ['Confirmações de', 'Agenda de hoje', 'Horários livres', 'Feitas hoje', 'Thiago Bastos'].forEach(function (x) { tem(x); });
  T.ACOES['abrir-grupo'](botao('abrir-grupo', { gid: 'recepcao' }));
  if (T.estado.grupo !== 'recepcao' || T.atual() !== null) throw new Error('o grupo abriu com uma tarefa aberta');
  tem('Voltar ao Copiloto'); tem('data-acao="aba-grupo"');
  T.ACOES['aba-grupo'](botao('aba-grupo', { atrib: 'homecare' }));
  if (!T.visiveis().length || T.visiveis().some(function (c) { return (c.atribs || [c.atrib]).indexOf('homecare') < 0; })) throw new Error('a aba Home care mostrou outra área');
  T.ACOES['aba-grupo'](botao('aba-grupo', { atrib: '' }));
  T.ACOES['abrir-tarefa'](botao('abrir-tarefa', { id: 'thiago' })); tem('modal-tarefa'); tem('Ele veio?');
  T.ACOES['abrir-tarefa'](botao('abrir-tarefa', { id: 'thiago' }));
  T.ACOES['abrir-grupo'](botao('abrir-grupo', { gid: 'confirmacoes' }));
  if (!T.atual() || T.atual().id !== 'confirmacoes') throw new Error('as confirmações não abriram no grupo');
  tem('conf-linha'); tem('por profissional'); naoTem('modal-tarefa');
  T.ACOES['abrir-grupo'](botao('abrir-grupo', { gid: '' }));
  if (T.estado.grupo !== null || T.atual() !== null) throw new Error('não voltou ao Copiloto');
});
run('Luiz, 09/10 · Copiloto no iPhone: uma linha por grupo, sem os tipos; tocar abre a lista, e a tarefa abre no lugar', function () {
  T.definirModo('iphone');
  if (!document.body.classList.contains('modo-iphone')) throw new Error('sem modo iPhone');
  location.hash = 'copiloto'; T.ACOES['abrir-grupo'](botao('abrir-grupo', { gid: '' }));
  tem('data-gid="acompanhamento"'); naoTem('modal-tarefa');
  T.ACOES['abrir-grupo'](botao('abrir-grupo', { gid: 'acompanhamento' })); tem('Juliana Prado');
  T.ACOES['abrir-tarefa'](botao('abrir-tarefa', { id: 'juliana' })); tem('tarefa--aberta'); naoTem('modal-tarefa');
  T.ACOES['abrir-grupo'](botao('abrir-grupo', { gid: '' }));
  T.ACOES.selecionar(botao('selecionar', { id: 'thiago' }));
  if (T.estado.grupo !== 'urgentes') throw new Error('a tarefa escolhida fora do grupo não abriu no grupo dela: ' + T.estado.grupo);
  tem('tarefa--aberta');
  T.ACOES['abrir-grupo'](botao('abrir-grupo', { gid: '' }));
  T.definirModo('notebook');
});
run('Luiz, 07/10 · lançamento: a Fer lança o pacote e os recebimentos; a análise do financeiro fica com a Dra. Suellen, sem tarefa de cobrança', function () {
  location.hash = 'copiloto';
  naoTem('Financeiro'); naoTem('Parcela'); naoTem('parcela');
  // Mudou de propósito nas etapas 2 e 3: entram Rubens, Carla, Eduardo Viana e Helena no Acompanhamento.
  tem('data-gid="acompanhamento"><span class="lg-qtd num" title="12 tarefas">12<'); // desde 09/10/2026, as contagens ficam nas linhas dos grupos
  location.hash = 'atribuicoes';
  naoTem('Financeiro'); naoTem('parcela');
  tem('Você lança o pacote e os recebimentos; a análise do financeiro fica com a Dra. Suellen, na Gestão.'); tem('lançar o pacote de quem fechou');
  location.hash = 'copiloto';
});
run('Luiz, 07/10 · marketing: a programação das próximas duas semanas; a Fer confere e manda todas, a doutora escolhe, ajusta ou sugere a legenda; vão e voltam até o consenso', function () {
  location.hash = 'copiloto';
  // Mudou de propósito em 07/10, na rodada b: a pauta da semana virou a programação das próximas duas semanas, com 4 peças;
  // a linha Parcerias está em espera, e o convite e o Google do Jorge saíram (o Google vai no dia da reavaliação).
  T.ACOES['abrir-grupo'](botao('abrir-grupo', { gid: 'administrativo' })); tem('Programação das próximas duas semanas'); tem('Programação de 2 semanas'); tem('4 peças para montar e mandar à doutora'); T.ACOES['abrir-grupo'](botao('abrir-grupo', { gid: '' })); // desde 09/10/2026, a programação fica no grupo Administrativo
  naoTem('Pauta de marketing da semana'); naoTem('Convite de indicação'); naoTem('Navalha de Ouro');
  T.ACOES['abrir-tarefa'](botao('abrir-tarefa', { id: 'pauta' }));
  tem('A programação das próximas duas semanas · confira e mande as 4 peças para a doutora'); tem('Falta a foto da sala de fototerapia'); tem('Mandar as 4 peças para a doutora'); tem('WhatsApp, para os pacientes'); tem('A linha Parcerias está em espera');
  naoTem('data-acao="peca" '); naoTem('Vai para a doutora');
  var pauta = T.estado.fila.filter(function (c) { return c.id === 'pauta'; })[0];
  if (pauta.pecas.map(function (x) { return x.id; }).join() !== 'p1,p3,p4,p5' || pauta.pecas.some(function (x) { return !(x.dia >= 1 && x.dia <= 14); })) throw new Error('as 4 peças das duas semanas');
  if (pauta.pecas.some(function (x) { return /parceir/i.test(x.tipo + ' ' + x.titulo); })) throw new Error('a linha Parcerias está em espera');
  // Na tela, as peças vêm na ordem das datas: a peça para os pacientes (amanhã) primeiro.
  var ordem = (__els.tela.innerHTML.match(/data-acao="abrir-peca-f" data-id="(p\d)"/g) || []).map(function (x) { return x.slice(-3, -1); }).filter(function (x, i, l) { return l.indexOf(x) === i; }).join();
  if (ordem !== 'p5,p1,p3,p4') throw new Error('ordem das peças: ' + ordem);
  T.aprovarAtual();
  if (pauta.etapa !== 'doutora' || pauta.status !== 'pendente') throw new Error('foi para a doutora: ' + pauta.etapa);
  tem('Com a Dra. Suellen: ela escolhe cada peça, ajusta a legenda e publica'); tem('Só na demonstração'); tem('A doutora devolve');
  T.aprovarAtual(); if (pauta.etapa !== 'doutora') throw new Error('espera a doutora');
  // A devolutiva: outra versão, falta foto, não vai e vai, com a legenda ajustada pela doutora (Luiz, 07/10/2026).
  T.ACOES['pauta-devolutiva-demo'](botao('pauta-devolutiva-demo'));
  if (pauta.etapa !== 'devolutiva') throw new Error('devolutiva: ' + pauta.etapa);
  var dec = pauta.pecas.map(function (x) { return x.id + ':' + x.escolha; }).join();
  if (dec !== 'p1:versao,p3:foto,p4:nao,p5:vai') throw new Error('decisões: ' + dec);
  if (!pauta.pecas[3].ajustada) throw new Error('a doutora ajusta a legenda e já aprova');
  tem('Não vai'); tem('Falta foto'); tem('Outra versão: mudar o texto'); tem('Vai: você manda aos pacientes'); tem('Legenda ajustada pela doutora');
  tem('2 peças voltaram da doutora para você'); tem('Mandar de novo para a doutora');
  T.ACOES['pauta-enviar'](botao('pauta-enviar')); if (pauta.etapa !== 'devolutiva') throw new Error('não manda antes de terminar o que voltou');
  T.ACOES['peca-foto'](botao('peca-foto', { id: 'p3' }));
  T.ACOES['peca-versao'](botao('peca-versao', { id: 'p1', pedido: 'texto' }));
  if (!pauta.pecas[0].corrigida || !pauta.pecas[1].corrigida || pauta.pecas[0].versao !== 2) throw new Error('correções');
  T.ACOES['peca-versao'](botao('peca-versao', { id: 'p5', pedido: 'texto' })); if (pauta.pecas[3].versao !== 1) throw new Error('o que vai não volta para a Fer');
  tem('Pronta para a doutora ver de novo');
  T.aprovarAtual();
  if (pauta.etapa !== 'doutora' || pauta.rodada !== 2 || pauta.pecas[0].escolha || pauta.pecas[3].escolha !== 'vai') throw new Error('segunda rodada');
  T.ACOES['pauta-devolutiva-demo'](botao('pauta-devolutiva-demo'));
  if (pauta.etapa !== 'consenso') throw new Error('consenso: ' + pauta.etapa);
  tem('Consenso com a doutora'); tem('Vai: a doutora publica'); tem('Mandei aos pacientes'); tem('Copiar a mensagem dos pacientes');
  T.ACOES['pauta-copiar'](botao('pauta-copiar'));
  T.aprovarAtual();
  if (pauta.status !== 'feita' || pauta.resultado.texto.indexOf('Consenso com a doutora: 3 peças vão') < 0 || !pauta.pecas[3].enviado) throw new Error('pauta: ' + pauta.status + ' ' + (pauta.resultado && pauta.resultado.texto));
  if (!T.estado.log.some(function (l) { return /A doutora publica as 2 do Instagram no dia\. A Fer mandou a do WhatsApp aos pacientes\./.test(l[1]); })) throw new Error('registro do fechamento');
  location.hash = 'atribuicoes';
  tem('Marketing'); tem('programação fechada com a doutora'); tem('a Dra. Suellen escolhe cada peça, ajusta a legenda e publica no Instagram'); tem('A linha Parcerias está em espera');
  location.hash = 'copiloto';
});
run('marketing: a Fer vê cada peça como vai ficar, manda a foto e pede outra versão enquanto monta', function () {
  location.hash = 'copiloto';
  var pauta = T.estado.fila.filter(function (c) { return c.id === 'pauta'; })[0];
  pauta.status = 'pendente'; pauta.etapa = 'montar'; pauta.rodada = 1;
  pauta.pecas.forEach(function (x) { x.escolha = null; x.corrigida = false; x.enviado = false; x.versao = 1; x.slide = 0; if (x.falta) x.fotoOk = false; });
  T.estado.selecionada = 'pauta';
  T.render(false);
  tem('Ver a peça'); tem('Ver e mandar a foto');
  T.ACOES['abrir-peca-f'](botao('abrir-peca-f', { id: 'p1' }));
  tem('1 de 5', painel()); tem('Próxima foto', painel()); tem('Legenda escrita pelo copiloto', painel()); tem('A conferência fixa não apontou nada', painel());
  tem('Você confere e manda todas para a doutora', painel()); naoTem('Vai para a doutora', painel());
  T.ACOES['slide-f'](botao('slide-f', { id: 'p1', passo: '1' }));
  if (pauta.pecas[0].slide !== 1) throw new Error('não passou a foto');
  tem('A conversa', __els['carrossel-f-p1'].innerHTML);
  __handlers.keydown[0]({ key: 'ArrowRight', target: { closest: function () { return null; } }, preventDefault: function () {} });
  if (pauta.pecas[0].slide !== 2) throw new Error('seta do teclado não passou');
  T.ACOES['peca-versao'](botao('peca-versao', { id: 'p1', pedido: 'texto' }));
  if (pauta.pecas[0].versao !== 2) throw new Error('sem versão 2');
  tem('Versão 2 do copiloto', painel()); tem('O que acontece na sua avaliação capilar', painel()); tem('1 de 5', painel()); tem('Voltar para a versão 1', painel());
  T.ACOES['abrir-peca-f'](botao('abrir-peca-f', { id: 'p3' }));
  tem('Escolher a foto', painel()); tem('Falta a foto', painel());
  T.ACOES['peca-foto'](botao('peca-foto', { id: 'p3', painel: '1' }));
  if (!pauta.pecas[1].fotoOk) throw new Error('foto não chegou');
  tem('Foto recebida', painel());
  T.ACOES['abrir-peca-f'](botao('abrir-peca-f', { id: 'p5' }));
  tem('WhatsApp, para os pacientes', painel()); tem('Mensagem escrita pelo copiloto', painel()); tem('Os cuidados depois da sessão, por escrito', painel());
  T.ACOES.fechar();
  T.ACOES['pauta-enviar'](botao('pauta-enviar'));
  if (pauta.etapa !== 'doutora') throw new Error('não foi para a doutora');
  T.ACOES['pauta-devolutiva-demo'](botao('pauta-devolutiva-demo'));
  // Com a foto e a versão 2 já prontas, a doutora só tira o vídeo e ajusta a legenda dos pacientes: consenso na primeira rodada.
  if (pauta.etapa !== 'consenso') throw new Error('consenso direto: ' + pauta.etapa);
  T.aprovarAtual();
  if (pauta.status !== 'feita') throw new Error('pauta não fechou');
});
run('marketing: a Fer pede a autorização do resultado', function () {
  // Mudou de propósito em 07/10, na rodada b: o pedido de autorização do Jorge vai sozinho; as fotos do antes e agora ele já recebeu no dia da reavaliação.
  location.hash = 'copiloto';
  T.ACOES.selecionar(botao('selecionar', { id: 'autorizacao' }));
  tem('sem o seu nome'); tem('se preferir não, sem problema nenhum'); naoTem('Três motivos, um cartão só');
  T.aprovarAtual();
  var c = T.estado.fila.filter(function (x) { return x.id === 'autorizacao'; })[0];
  if (c.status !== 'feita') throw new Error('pedido não saiu');
  var p = T.pacientePorNome('Jorge Almeida');
  if (!p.termos.post || p.termos.post.pedido !== 0) throw new Error('o pedido não ficou registrado na ficha');
  // Na ficha, só a autorização de publicação: assinada e revogada, com registro (os termos ficam para depois, Luiz, 07/10/2026).
  T.ACOES.ficha(botao('ficha', { nome: 'Jorge Almeida' }));
  tem('Pedida hoje: aguardando', painel()); tem('Registrar assinatura', painel()); naoTem('Termo de tratamento', painel());
  T.ACOES['ficha-termo'](botao('ficha-termo', { termo: 'post', op: 'assinar' }));
  if (p.termos.post.em !== 0 || !T.estado.auditoria.some(function (x) { return x.cod === p.cod && x.texto === 'registrou a assinatura da autorização de publicação' && x.antes === 'pedida hoje'; })) throw new Error('assinatura da autorização');
  tem('Assinada hoje', painel());
  T.ACOES['ficha-termo'](botao('ficha-termo', { termo: 'post', op: 'revogar' }));
  tem('Revogada hoje', painel()); tem('Nenhum post com as fotos', painel());
  T.ACOES.fechar();
});
run('tela de atribuições', function () { location.hash = 'atribuicoes'; tem('Tudo o que é seu, por área.'); tem('Confirmações'); tem('Ponte com a médica'); tem('horários de atendimento'); });
run('agenda de hoje', function () { T.ACOES['ir-data'](botao('ir-data', { data: T.K[0] })); tem('Ana Carolina'); tem('Dra. Monielle'); tem('Beatriz Portela'); tem('Natália Freitas'); tem('Em atendimento'); });
run('agenda de amanhã: a médica não atende', function () { T.ACOES['ir-data'](botao('ir-data', { data: T.K[1] })); tem('sem confirmar'); tem('Horário vago'); tem('não atende'); tem('Próximo dia:'); });
run('agenda do 3º dia', function () { T.ACOES['ir-data'](botao('ir-data', { data: T.K[2] })); tem('Lia Fagundes'); tem('consulta médica'); });
run('agenda em linhas: uma linha por horário, com as três profissionais lado a lado, inclusive os vagos e quem não atende', function () {
  // Terça (hoje): a Ana Carolina e a Dra. Suellen de manhã, e a Dra. Monielle só à tarde.
  T.ACOES['ir-data'](botao('ir-data', { data: T.K[0] }));
  var h = __els.tela.innerHTML;
  tem('class="ag-grade"', h); naoTem('coluna-prof', h); naoTem('data-acao="prof-agenda"', h);
  var cabs = h.match(/<th scope="col" class="ag-cab[^"]*">/g) || [];
  if (cabs.length !== 4) throw new Error('cabeçalho: hora e três profissionais, achei ' + cabs.length);
  ['Ana Carolina', 'Dra. Suellen', 'Dra. Monielle'].forEach(function (n) { tem('<span id="prof-' + { 'Ana Carolina': 'ana', 'Dra. Suellen': 'suellen', 'Dra. Monielle': 'moni' }[n] + '">' + n + '</span>', h); });
  var linhas = h.match(/<tr class="ag-linha[^"]*" data-hora="(\d\d:\d\d)">/g) || [];
  var horas = linhas.map(function (l) { return /data-hora="([^"]+)"/.exec(l)[1]; });
  var todas = {};
  ['ana', 'suellen', 'moni'].forEach(function (p) { T.gradeDoDia(T.K[0], p).linhas.forEach(function (l) { todas[l.hora] = 1; }); });
  var esperadas = Object.keys(todas).sort();
  if (horas.join() !== esperadas.join()) throw new Error('uma linha por horário: ' + horas.join() + ' × ' + esperadas.join());
  if (horas.length < 5) throw new Error('poucas linhas: ' + horas.length);
  // Cada linha tem a hora e uma célula por profissional (ou é coberta por um "não atende" que ocupa várias linhas).
  var corpo = h.split('<tbody>')[1];
  var trs = corpo.split('<tr class="ag-linha').slice(1);
  var ocupadas = [0, 0, 0];
  trs.forEach(function (tr, i) {
    var tds = tr.split('</tr>')[0].match(/<td class="ag-cel[^>]*>/g) || [];
    var conta = tds.length + ocupadas.filter(function (x) { return x > 0; }).length;
    ocupadas = ocupadas.map(function (x) { return x > 0 ? x - 1 : 0; });
    var j = 0;
    tds.forEach(function (td) { while (ocupadas[j] > 0) j += 1; var r = /rowspan="(\d+)"/.exec(td); if (r) ocupadas[j] = Number(r[1]) - 1; j += 1; });
    if (conta !== 3) throw new Error('linha ' + horas[i] + ' com ' + conta + ' células');
  });
  tem('ag-cel--off', h); tem('Não atende', h);
  tem('data-prof="ana" data-hora="', h);
  // As células trazem o que já se via: nome, tipo, situação e ações.
  tem('Beatriz Portela', h); tem('Em atendimento', h);
  // Amanhã há horário vago: aparece na célula, com o botão de ver quem chamar, e a Dra. Monielle não atende o dia todo.
  T.ACOES['ir-data'](botao('ir-data', { data: T.K[1] }));
  h = __els.tela.innerHTML;
  tem('ag-cel ag-cel--vaga', h); tem('Horário vago', h); tem('data-acao="abrir-vaga"', h); tem('ag-cel--folga', h);
  tem('Pedir confirmação', h); naoTem('data-acao="conf-enviar"', h);
  if (!/<td class="ag-cel ag-cel--off ag-cel--folga" rowspan="\d+">/.test(h)) throw new Error('a folga do dia todo vira uma célula só');
  // Horário livre: a célula da profissional naquele horário tem o botão Marcar.
  var s = T.livresAPartir('ana', daquiA(20), 1)[0];
  T.ACOES['ir-data'](botao('ir-data', { data: s.data }));
  tem('data-acao="marcar" data-data="' + s.data + '" data-hora="' + s.hora + '" data-prof="ana"');
  // Navegação de dias, semana e atalhos continuam.
  tem('data-acao="dia-passo"', h); tem('dia-mini', h); tem('+15 dias', h); tem('+60 dias', h); tem('Horários da Dra. Monielle', h);
  // No modo iPhone, a mesma grade, sem seletor de profissional.
  T.definirModo('iphone'); location.hash = 'agenda';
  h = __els.tela.innerHTML; tem('class="ag-grade"', h); naoTem('profs-filtro', h);
  T.definirModo('notebook');
});
run('agenda: +30 dias com horários livres', function () { T.ACOES['pular-dias'](botao('pular-dias', { dias: '30' })); tem('daqui a'); tem('data-acao="marcar"'); tem('+45 dias'); tem('dia-mini'); });
run('agenda: dia seguinte', function () { var antes = T.estado.dataAgenda; T.ACOES['dia-passo'](botao('dia-passo', { passo: '1' })); if (T.estado.dataAgenda <= antes) throw new Error('não andou'); });
run('agenda: escolher data no campo', function () { __handlers.change[0]({ target: { matches: function (s) { return s === 'input[data-ir-data]'; }, value: T.K[2], dataset: {} } }); if (T.estado.dataAgenda !== T.K[2]) throw new Error('não foi para a data'); });
run('agenda: marcar daqui a 45 dias', function () {
  var s = T.livresAPartir('ana', daquiA(45), 1)[0];
  if (!s) throw new Error('sem horário livre');
  T.ACOES.marcar(botao('marcar', { data: s.data, hora: s.hora, prof: 'ana' }));
  tem('Para quem?', painel()); tem('Clinicorp', painel());
  var quem = new El('quem'); quem.value = 'Teste Fictício';
  T.ACOES['confirmar-marcacao'](botao('confirmar-marcacao', { data: s.data, hora: s.hora, prof: 'ana' }, ctxCom({ '.painel-corpo': new El('pc'), '#f-quem': quem, '.opcao[data-grupo="tipo"][aria-pressed="true"]': opc('Sessão', 'Sessão') })));
  if (!T.estado.agenda.some(function (a) { return a.nome === 'Teste Fictício' && a.data === s.data && a.hora === s.hora; })) throw new Error('não marcou');
  T.ACOES['ir-data'](botao('ir-data', { data: s.data })); tem('Marcado agora'); tem('Teste Fictício');
});
run('configurações pela engrenagem do topo', function () { T.ACOES['ir-config'](botao('ir-config')); tem('Configurações do sistema'); tem('Profissionais e horários de atendimento'); tem('Regras e prazos da fila'); tem('Clinicorp'); });
run('horários: a médica passa a atender também às segundas', function () {
  T.ACOES.config(botao('config', { prof: 'moni' }));
  tem('Pacientes no mesmo horário'); tem('Atende terça e quinta');
  T.ACOES['config-periodo'](botao('config-periodo', { wd: '1', per: 'tarde' }));
  T.ACOES['config-salvar'](botao('config-salvar'));
  if (!T.estado.config.moni.dias[1] || !T.estado.config.moni.dias[1].tarde) throw new Error('não salvou');
  var seg = daquiA(((8 - new Date().getDay()) % 7) || 7);
  if (!T.gradeDoDia(chaveDe(seg), 'moni').linhas.length) throw new Error('segunda continua sem horário');
});
run('horários: estender a tarde da Ana Carolina até 19h', function () {
  T.ACOES.config(botao('config', { prof: 'ana' }));
  __handlers.change[0]({ target: { matches: function (s) { return s === 'input[data-config]'; }, value: '19:00', dataset: { config: 'tarde-1' } } });
  T.ACOES['config-salvar'](botao('config-salvar'));
  if (T.estado.config.ana.tarde[1] !== '19:00') throw new Error('não mudou');
});
run('horários: período inválido não salva', function () {
  T.ACOES.config(botao('config', { prof: 'suellen' }));
  __handlers.change[0]({ target: { matches: function (s) { return s === 'input[data-config]'; }, value: '13:00', dataset: { config: 'tarde-1' } } });
  T.ACOES['config-salvar'](botao('config-salvar', {}, ctxCom({ '.painel-corpo': new El('pc') })));
  if (T.estado.config.suellen.tarde[1] === '13:00') throw new Error('salvou horário inválido');
  tem('Mudanças ainda não salvas');
  T.ACOES['config-descartar'](botao('config-descartar'));
  if (T.estado.rascunho.suellen.tarde[1] !== '17:00') throw new Error('não descartou');
});
function itemDe(id) { return T.estado.agenda.filter(function (a) { return a.id === id; })[0]; }
run('confirmações: uma linha por paciente na fila', function () {
  location.hash = 'copiloto'; T.ACOES.filtrar(botao('filtrar', { atrib: 'confirmacoes' }));
  tem('Uma mensagem para cada paciente'); tem('data-acao="conf-enviar"'); tem('Prefere ligação'); tem('Primeira vez na clínica'); tem('Pedir uma por uma (5)'); tem('data-acao="copiar-msg" data-fonte="conf"'); tem('Enviei<span class="tecla">A</span>'); tem('exames de sangue feitos nos últimos 90 dias'); naoTem('exames recentes'); // mudou de propósito na etapa 4: a Paula recebe as boas-vindas
});
run('confirmações: tecla A envia só a próxima', function () { T.aprovarAtual(); if (itemDe('1-ana-1000-vinícius').conf !== 'enviada') throw new Error('não enviou a do Vinícius'); if (itemDe('1-ana-1400-murilo').conf !== 'a-enviar') throw new Error('enviou mais de uma'); });
run('confirmações: editar e enviar a de um paciente', function () {
  var id = '1-ana-1400-murilo';
  T.ACOES['conf-editar'](botao('conf-editar', { id: id })); tem('conf-txt-' + id);
  getEl('conf-txt-' + id).value = 'Oi, Murilo! Sua primeira sessão é amanhã às 14h. Confirma?';
  T.ACOES['conf-salvar'](botao('conf-salvar', { id: id }));
  var a = itemDe(id);
  if (a.conf !== 'enviada' || !a.confEditado || a.textoConf.indexOf('primeira sessão') < 0) throw new Error(JSON.stringify(a));
});
run('confirmações: quem prefere ligação', function () {
  var id = '1-ana-1700-rogério';
  T.ACOES['conf-ligar'](botao('conf-ligar', { id: id })); if (itemDe(id).conf !== 'ligar') throw new Error('não foi para ligar');
  tem('(41) 90000-0666');
  T.ACOES['conf-nao-atendeu'](botao('conf-nao-atendeu', { id: id }));
  var a = itemDe(id);
  if (a.conf !== 'a-enviar' || a.textoConf.indexOf('Tentei te ligar') < 0) throw new Error('sem mensagem depois da ligação');
});
run('confirmações: pedidos enviados não fecham o cartão', function () {
  T.ACOES['conf-todas'](botao('conf-todas'));
  tem('Pedido 1 de'); tem('data-acao="conf-seq-enviei"'); tem('Parar por aqui');
  var guarda = 0;
  while (T.estado.confSeq && guarda < 12) { guarda += 1; T.ACOES['conf-seq-enviei'](botao('conf-seq-enviei', { id: T.proximasDaSequencia()[0].id })); }
  if (T.estado.confSeq) throw new Error('a sequência não acabou');
  var c = T.estado.fila.filter(function (x) { return x.id === 'confirmacoes'; })[0];
  if (c.status !== 'pendente' || !c.aguardando) throw new Error('o cartão fechou sem as respostas: ' + c.status);
  if (T.estado.fila[T.estado.fila.length - 1] !== c) throw new Error('não foi para o fim da fila');
  tem('Aguardando a resposta de'); tem('Aguardando resposta'); tem('Ligar para quem não respondeu'); tem('Mensagem enviada não é confirmação');
});
run('confirmações: ver depois mostra o próximo cartão', function () {
  T.ACOES['conf-depois'](botao('conf-depois'));
  var aberta = T.atual();
  if (aberta && aberta.id === 'confirmacoes') throw new Error('continuou no cartão das confirmações');
  T.ACOES.filtrar(botao('filtrar', { atrib: 'confirmacoes' }));
});
run('confirmações: painel com resposta de cada paciente', function () { T.ACOES.confirmacoes(botao('confirmacoes')); tem('Confirmações de', painel()); tem('Quer remarcar', painel()); tem('não atende', painel()); tem('aguardando resposta', painel()); });
run('confirmações: confirmou', function () { T.ACOES['conf-ok'](botao('conf-ok', { id: '1-ana-0900-larissa' })); if (itemDe('1-ana-0900-larissa').conf !== 'confirmou') throw new Error('não confirmou'); });
run('confirmações: cancelou vira horário vago', function () {
  var antes = T.estado.cancelados.length;
  T.ACOES['conf-cancelou'](botao('conf-cancelou', { id: '1-ana-1000-vinícius' }));
  if (T.estado.cancelados.length !== antes + 1) throw new Error('não cancelou');
  var linha = T.gradeDoDia(T.K[1], 'ana').linhas.filter(function (l) { return l.hora === '10:00'; })[0];
  if (!linha.vaga) throw new Error('o horário não virou vago');
  tem('Mudanças de hoje', painel());
});
run('confirmações: quer remarcar', function () {
  var id = '1-ana-1400-murilo';
  T.ACOES['conf-remarcar'](botao('conf-remarcar', { id: id })); tem('Para quando?', painel());
  var alvo = new El('alvo');
  T.ACOES['prox-intervalo'](botao('prox-intervalo', { dias: '7', alvoGrupo: 'remarcar', prof: 'ana', pref: '14:00' }, ctxCom({ '[data-horarios="remarcar"]': alvo })));
  var m = /data-valor="(\d{4}-\d{2}-\d{2}\|\d{2}:\d{2})"/.exec(alvo.innerHTML);
  if (!m) throw new Error('sem horários para remarcar');
  T.ACOES['conf-remarcar-ok'](botao('conf-remarcar-ok', { id: id }, ctxCom({ '.opcao[data-grupo="remarcar"][aria-pressed="true"]': opc(m[1], 'x') })));
  var a = itemDe(id);
  if (a.data === T.K[1] || !a.novo) throw new Error('não remarcou');
  var linha = T.gradeDoDia(T.K[1], 'ana').linhas.filter(function (l) { return l.hora === '14:00'; })[0];
  if (!linha.vaga) throw new Error('o horário antigo não virou vago');
  T.ACOES.fechar();
});
run('confirmações: na agenda de amanhã', function () { T.ACOES['ir-data'](botao('ir-data', { data: T.K[1] })); tem('Horário vago'); tem('Confirmou'); tem('Aguardando resposta'); });
run('confirmações: sem resposta vira ligação e o cartão só fecha com todas as respostas', function () {
  T.ACOES['conf-ligar-todos'](botao('conf-ligar-todos'));
  var ids = ['1-ana-1600-elaine', '1-ana-1700-rogério', '1-suellen-1000-cristina', '1-suellen-1400-paula'];
  ids.forEach(function (id) { if (itemDe(id).conf !== 'ligar') throw new Error(id + ' não foi para ligação'); });
  var c = T.estado.fila.filter(function (x) { return x.id === 'confirmacoes'; })[0];
  ids.slice(0, 3).forEach(function (id) { T.ACOES['conf-ok'](botao('conf-ok', { id: id })); });
  if (c.status !== 'pendente') throw new Error('fechou faltando a Paula');
  T.ACOES['conf-ok'](botao('conf-ok', { id: ids[3] }));
  if (c.status !== 'feita' || c.resultado.texto.indexOf('Todos responderam') < 0) throw new Error('não fechou com todas as respostas: ' + c.status);
});
run('volta para tudo', function () { T.ACOES.filtrar(botao('filtrar', { atrib: '' })); if (T.estado.grupo !== null) throw new Error('ficou no grupo'); tem('data-gid="acompanhamento"'); T.ACOES['abrir-grupo'](botao('abrir-grupo', { gid: 'acompanhamento' })); tem('Juliana Prado'); T.ACOES['abrir-grupo'](botao('abrir-grupo', { gid: '' })); }); // mudou de propósito em 09/10/2026: a lista inteira fica nos grupos
run('Luiz, 07/10 · ligação: sem “Relatou sintoma”; o lembrete manda passar direto à Dra. Monielle, e nada fica registrado', function () {
  T.ACOES.selecionar(botao('selecionar', { id: 'rubens' }));
  naoTem('data-r="sintoma"'); naoTem('Relatou sintoma'); tem('Se a pessoa contar um sintoma ou perguntar de remédio, não oriente: passe direto para a Dra. Monielle, no mesmo dia. Nada fica registrado aqui.');
  if ('alertas' in T.estado) throw new Error('relato não se registra no sistema');
});
run('ligação: não atendeu', function () { T.ACOES.selecionar(botao('selecionar', { id: 'eduardov' })); T.ACOES.desfecho(botao('desfecho', { r: 'nao-atendeu' }, ctxCom({ '.sub': new El('sub') }))); if (!T.estado.fila.some(function (c) { return c.id === 'eduardov-msg'; })) throw new Error('sem seguimento'); });
run('ligação: remarcou num horário livre da agenda', function () {
  T.ACOES.selecionar(botao('selecionar', { id: 'juliana' }));
  var c = T.atual();
  var alt = c.opcoesRemarcar[1];
  if (alt[0].indexOf('|') < 0) throw new Error('alternativa sem data');
  var sub = new El('sub');
  T.ACOES.desfecho(botao('desfecho', { r: 'remarcou' }, ctxCom({ '.sub': sub })));
  T.ACOES['concluir-desfecho'](botao('concluir-desfecho', { r: 'remarcou' }, ctxCom({ '.opcao[data-grupo="slot"][aria-pressed="true"]': opc(alt[0], alt[1]) })));
  var p = alt[0].split('|');
  if (!T.estado.agenda.some(function (a) { return a.nome === 'Juliana Prado' && a.data === p[0] && a.hora === p[1]; })) throw new Error('não foi para a agenda');
});
// Mudou de propósito: a mensagem do Thiago só existe depois de a Fer responder "Não veio".
run('aprovar mensagem', function () {
  T.ACOES.selecionar(botao('selecionar', { id: 'thiago' }));
  T.ACOES.presenca(botao('presenca', { r: 'faltou', agenda: '0-ana-0900-thiago' }));
  T.aprovarAtual();
  var f = T.estado.fila.filter(function (c) { return c.id === 'falta-0-ana-0900-thiago'; })[0];
  if (!f || f.status !== 'feita') throw new Error('a mensagem da falta não foi aprovada');
});
run('editar e salvar', function () { T.ACOES.selecionar(botao('selecionar', { id: 'marcos' })); T.editarAtual(); tem('Editando a mensagem'); T.ACOES['salvar-edicao'](botao('salvar-edicao')); if (T.estado.vagas.v5.estado !== 'convidado') throw new Error('v5 sem convite'); });
run('pular e restaurar', function () { T.ACOES.selecionar(botao('selecionar', { id: 'simone' })); T.pularAtual(); location.hash = 'feito'; tem('Puladas'); tem('Simone Tavares'); T.ACOES.restaurar(botao('restaurar', { id: 'simone' })); if (T.estado.fila.find(function (c) { return c.id === 'simone'; }).status !== 'pendente') throw new Error('não voltou para a lista'); });
run('vaga: abrir e convidar', function () { T.ACOES['abrir-vaga'](botao('abrir-vaga', { id: 'v2' })); tem('Peça mais opções ao copiloto', painel()); tem('Leandro Schmitt', painel()); T.ACOES.convidar(botao('convidar', { vaga: 'v2', pessoa: 'leandro' })); if (T.estado.vagas.v2.estado !== 'convidado') throw new Error('v2'); });
run('vaga: não pode', function () { T.ACOES['vaga-recusou'](botao('vaga-recusou', { id: 'v2' })); if (T.estado.vagas.v2.estado !== 'livre') throw new Error('v2 não voltou'); });
run('chat: abre', function () { T.ACOES.chat(botao('chat')); tem('Conversar com o copiloto', painel()); tem('Oi, Fer!', painel()); tem('Livres daqui a 30 dias', painel()); });
run('chat: mais opções para amanhã', function () { T.ACOES['chat-sugestao'](botao('chat-sugestao', { pergunta: 'Não consegui encaixar ninguém nos horários vagos de amanhã. Me dá mais opções?' })); __flush(); tem('Débora Assis', ultimaMsg()); });
run('chat: aprova convite na conversa', function () { T.ACOES.convidar(botao('convidar', { vaga: 'v2', pessoa: 'debora', __chat: true })); if (T.estado.vagas.v2.pessoa !== 'debora') throw new Error('débora'); if (__els.painel.hidden) throw new Error('fechou o chat'); tem('Convite enviado', ultimaMsg()); });
run('chat: livres daqui a 30 dias', function () { T.ACOES['chat-sugestao'](botao('chat-sugestao', { pergunta: 'Quais os primeiros horários livres da Ana Carolina daqui a 30 dias?' })); __flush(); tem('primeiros horários livres', ultimaMsg()); tem('Abrir esse dia na agenda', ultimaMsg()); });
run('chat: livres da médica em 45 dias', function () { var r = T.responder('Tem horário livre com a Dra. Monielle daqui a 45 dias?'); tem('Dra. Monielle', r.html); });
run('chat: confirmações', function () { T.ACOES['chat-sugestao'](botao('chat-sugestao', { pergunta: 'Quem ainda não confirmou amanhã?' })); __flush(); tem('pacientes:', ultimaMsg()); tem('não atende', ultimaMsg()); });
run('chat: três agendas', function () { T.ACOES['chat-sugestao'](botao('chat-sugestao', { pergunta: 'Como estão as agendas da Ana Carolina, da Dra. Suellen e da Dra. Monielle?' })); __flush(); tem('As três agendas', ultimaMsg()); tem('Consultas médicas', ultimaMsg()); tem('Atende', ultimaMsg()); });
run('Luiz, 07/10 · chat: dúvida de remédio não tem resposta da IA; o lembrete manda passar direto à Dra. Monielle', function () {
  T.ACOES['chat-sugestao'](botao('chat-sugestao', { pergunta: 'A Aline perguntou se pode tomar o Capilare Fort junto com outro remédio. O que eu respondo?' })); __flush();
  tem('sobre medicamento', ultimaMsg()); tem('quem responde é a Dra. Monielle', ultimaMsg()); tem('passe a mensagem direto para a Dra. Monielle, no mesmo dia, sem orientar', ultimaMsg()); tem('nada fica registrado aqui', ultimaMsg());
  naoTem('data-acao="chat-alerta"', ultimaMsg()); naoTem('Passei', ultimaMsg()); naoTem('Boa pergunta', ultimaMsg());
});
run('chat: sintoma no texto livre', function () { var r = T.responder('O Caio disse que está com coceira'); if (r.tipo !== 'clinico' || r.dados.remedio || r.dados.paciente !== 'Caio Ferraz') throw new Error(JSON.stringify(r.dados)); });
run('chat: texto livre pelo formulário', function () {
  var form = new El('form'); var campo = new El('campo'); campo.value = 'bom dia'; form.querySelector = function () { return campo; };
  __handlers.submit[0]({ target: { closest: function (s) { return s === '.chat-entrada' ? form : null; } }, preventDefault: function () {} });
  __flush();
  tem('No protótipo eu só respondo', ultimaMsg());
  if (campo.value !== '') throw new Error('campo não limpou');
});
run('chat: pedir pelo vago de depois de amanhã', function () { T.ACOES['chat-pedir'](botao('chat-pedir', { vaga: 'v3' })); __flush(); tem('Helena Brandão', ultimaMsg()); });
run('chat: pedir pelo vago da médica', function () { T.ACOES['chat-pedir'](botao('chat-pedir', { vaga: 'v6' })); __flush(); tem('não amplio', ultimaMsg()); });
run('chat: pedir pela reavaliação', function () { T.ACOES['chat-pedir'](botao('chat-pedir', { vaga: 'v4' })); __flush(); tem('Márcia Lopes', ultimaMsg()); });
run('vaga mostra quem o chat achou', function () { T.ACOES['abrir-vaga'](botao('abrir-vaga', { id: 'v3' })); tem('Helena Brandão', painel()); });
run('recepção', function () { T.ACOES.recepcao(botao('recepcao')); tem('Recepção de hoje', painel()); tem('Beatriz Portela', painel()); tem('Alta · manutenção a cada 45 dias', painel()); naoTem('Encaminhada à Dra. Monielle', painel()); });
run('chegada com cadastro a completar', function () { T.ACOES['rec-chegou'](botao('rec-chegou', { id: '0-suellen-1400-letícia' })); tem('Peça só isto hoje', painel()); T.ACOES['rec-registrar-chegada'](botao('rec-registrar-chegada', { id: '0-suellen-1400-letícia', pedidos: 'whats,origem' })); });
run('saída de avaliação: vai pensar, sem o campo de reação; o lembrete diz que o paciente fala direto com a Dra. Monielle', function () {
  T.ACOES['rec-saiu'](botao('rec-saiu', { id: '0-suellen-0900-beatriz' }));
  var h = painel();
  tem('Resultado da avaliação, que a Dra. Suellen passou', h); tem('Se fechou: o lançamento do pacote', h); tem('Primeira sessão com a Ana Carolina', h); tem('Home care que a Ana Carolina entregou', h);
  tem('Se o paciente contar um sintoma ou tiver dúvida sobre remédio, ele fala direto com a Dra. Monielle. A clínica não orienta e não registra nada no sistema.', h);
  naoTem('data-grupo="reacao"', h); naoTem('f-relato', h); naoTem('pesquisa', h);
  var ctx = ctxCom({ '.painel-corpo': new El('pc'), '.opcao[data-grupo="fechou"][aria-pressed="true"]': opc('pensar', 'Vai pensar'), 'TODOS .opcao[data-grupo="hc"][aria-pressed="true"]': [opc('nutri', 'Capilare Nutri')] });
  T.ACOES['rec-registrar-saida'](botao('rec-registrar-saida', { id: '0-suellen-0900-beatriz' }, ctx));
  var b = T.estado.agenda.filter(function (a) { return a.nome === 'Beatriz Portela'; })[0];
  if (b.status !== 'saiu' || 'reacao' in b) throw new Error('saída não registrada');
});
run('saída: próxima reavaliação daqui a 30 dias', function () {
  T.ACOES['rec-chegou'](botao('rec-chegou', { id: '0-suellen-1000-natália' }));
  T.ACOES['rec-registrar-chegada'](botao('rec-registrar-chegada', { id: '0-suellen-1000-natália', pedidos: '' }));
  T.ACOES['rec-saiu'](botao('rec-saiu', { id: '0-suellen-1000-natália' }));
  tem('Em 30 dias', painel()); tem('Outra data', painel());
  var alvo = new El('alvo');
  T.ACOES['prox-intervalo'](botao('prox-intervalo', { dias: '30', alvoGrupo: 'proxima', prof: 'suellen', pref: '10:00' }, ctxCom({ '[data-horarios="proxima"]': alvo })));
  tem('Horários livres', alvo.innerHTML);
  var m = /data-valor="(\d{4}-\d{2}-\d{2})\|(\d{2}:\d{2})"/.exec(alvo.innerHTML);
  if (!m) throw new Error('sem horário na lista');
  var ctx = ctxCom({ '.painel-corpo': new El('pc'), '.opcao[data-grupo="proxima"][aria-pressed="true"]': opc(m[1] + '|' + m[2], 'x'), '.opcao[data-grupo="google"][aria-pressed="true"]': opc('qr', 'Mostrei o QR Code') });
  T.ACOES['rec-registrar-saida'](botao('rec-registrar-saida', { id: '0-suellen-1000-natália' }, ctx));
  if (!T.estado.agenda.some(function (a) { return a.nome === 'Natália Freitas' && a.data === m[1] && a.proc === 'Reavaliação'; })) throw new Error('não marcou a próxima');
});
run('saída: outra data pelo campo', function () {
  T.ACOES['rec-chegou'](botao('rec-chegou', { id: '0-ana-1100-sérgio' }));
  // Protocolo não se pergunta ao paciente: a Dra. Suellen define, e a Fer lança (Luiz, 07/10/2026).
  tem('Não pergunte ao paciente: a Dra. Suellen define o protocolo', painel()); naoTem('Protocolo atual, sessões e home care', painel());
  T.ACOES['rec-registrar-chegada'](botao('rec-registrar-chegada', { id: '0-ana-1100-sérgio', pedidos: '' }));
  var alvo = new El('alvo');
  var ctx = ctxCom({ '[data-horarios="proxima"]': alvo });
  __handlers.change[0]({ target: { matches: function (s) { return s === 'input[data-prox-data]'; }, value: chaveDe(daquiA(40)), dataset: { alvoGrupo: 'proxima', prof: 'ana', pref: '11:00' }, closest: function (s) { return s === '[data-contexto]' ? ctx : null; } } });
  tem('Horários livres', alvo.innerHTML);
});
run('saída sem próxima data vira tarefa', function () {
  T.ACOES['rec-chegou'](botao('rec-chegou', { id: '0-ana-1000-caio' }));
  T.ACOES['rec-registrar-chegada'](botao('rec-registrar-chegada', { id: '0-ana-1000-caio', pedidos: '' }));
  T.ACOES['rec-saiu'](botao('rec-saiu', { id: '0-ana-1000-caio' }));
  var ctx = ctxCom({ '.painel-corpo': new El('pc'), '.opcao[data-grupo="proxima"][aria-pressed="true"]': opc('nenhuma', 'Não quis marcar agora') });
  T.ACOES['rec-registrar-saida'](botao('rec-registrar-saida', { id: '0-ana-1000-caio' }, ctx));
  var c = T.estado.fila.filter(function (x) { return x.id === 'sp-0-ana-1000-caio'; })[0];
  if (!c) throw new Error('sem tarefa');
  if (c.texto.indexOf('Tenho ') < 0) throw new Error('mensagem sem horários');
});
run('confirmou na agenda', function () { T.ACOES['ir-data'](botao('ir-data', { data: T.K[1] })); T.ACOES['conf-ok'](botao('conf-ok', { id: '1-ana-0900-larissa' })); tem('Confirmou'); });
run('recepção pela tela principal e pela agenda de hoje', function () { location.hash = 'copiloto'; tem('Recepção de hoje'); T.ACOES['ir-data'](botao('ir-data', { data: T.K[0] })); tem('data-acao="rec-chegou"'); tem('data-acao="rec-saiu"'); });
run('painéis de apoio', function () { T.ACOES['ver-log'](botao('ver-log')); T.ACOES.raiox(botao('raiox')); T.ACOES.resumo(botao('resumo')); tem('Resumo para segunda', painel()); });
run('atribuições depois do dia andar', function () { location.hash = 'atribuicoes'; tem('Tudo o que é seu'); });
run('atalho / abre a conversa', function () { T.ACOES.fechar(); __handlers.keydown[0]({ key: '/', target: { closest: function () { return null; } }, preventDefault: function () {}, ctrlKey: false, metaKey: false, altKey: false }); tem('Conversar com o copiloto', painel()); T.ACOES.fechar(); });
run('modo iPhone', function () { T.definirModo('iphone'); if (!document.body.classList.contains('modo-iphone')) throw new Error('sem modo'); T.definirModo('notebook'); });
run('reiniciar', function () { T.ACOES.reiniciar(botao('reiniciar')); tem('Bom dia, Fer.'); if (T.estado.chat.length !== 1) throw new Error('chat não zerou'); if (T.estado.config.ana.tarde[1] !== '18:00') throw new Error('horários não voltaram'); });

/* ---------- Etapa 1: Pacientes, ficha, chegada, ativo em risco, alertas e Configurações ---------- */
function soNomes(lista) { return lista.map(function (p) { return p.nome; }); }
function filtro(o) { return soNomes(T.filtrarPacientes(Object.assign({ busca: '', grupo: '', prof: '', origem: '', pend: '' }, o))); }
function contem(lista, nomes, oQue) { nomes.forEach(function (n) { if (lista.indexOf(n) < 0) throw new Error(oQue + ': falta ' + n + ' em ' + lista.join(', ')); }); }
run('pacientes: tela nova e base coerente com todos os nomes do protótipo', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  location.hash = 'pacientes';
  tem('Cada paciente num lugar só.'); tem('data-pac-busca'); tem('Profissional</option>'); tem('Origem</option>'); tem('Pendência</option>'); tem('Ativo em risco<span class="n">6</span>'); // mudou de propósito: entra o Rubens Teodoro
  var base = soNomes(T.estado.pacientes);
  var nomes = [];
  T.estado.fila.forEach(function (c) { if (c.tipo !== 'lote' && c.tipo !== 'pauta' && c.tipo !== 'missao') nomes.push(c.nome); });
  Object.keys(T.PESSOAS).forEach(function (k) { nomes.push(T.PESSOAS[k].nome); });
  T.estado.agenda.forEach(function (a) { if (!/\d$/.test(a.id)) nomes.push(a.nome); });
  var sem = nomes.filter(function (n) { return base.indexOf(n) < 0; });
  if (sem.length) throw new Error('sem cadastro na base: ' + sem.join(', '));
  if (base.length < 30) throw new Error('base pequena: ' + base.length);
  // O que a recepção pede hoje bate com o cadastro de cada um.
  T.estado.agenda.filter(function (a) { return a.data === T.K[0] && !/\d$/.test(a.id) && a.status !== 'saiu'; }).forEach(function (a) {
    var esperado = T.faltamDe(T.pacientePorNome(a.nome)).join();
    if (a.faltam.join() !== esperado) throw new Error(a.nome + ': recepção pede ' + a.faltam + ', cadastro diz ' + esperado);
  });
});
run('pacientes: busca sem acento, pelo final do telefone e pelo código', function () {
  contem(filtro({ busca: 'joao' }), ['João Grigoletto', 'Joao Grigoletto'], 'joao');
  contem(filtro({ busca: 'otavio' }), ['Otávio Reis'], 'otavio');
  contem(filtro({ busca: 'KUHN' }), ['Eduardo Kühn'], 'kuhn');
  contem(filtro({ busca: 'sato ricardo' }), ['Ricardo Sato'], 'duas palavras');
  if (filtro({ busca: '0348' }).join() !== 'Patrícia Lacerda') throw new Error('final 0348: ' + filtro({ busca: '0348' }));
  if (filtro({ busca: 'SF-0412' }).join() !== 'Ricardo Sato') throw new Error('código: ' + filtro({ busca: 'SF-0412' }));
  if (filtro({ busca: '(41) 90000-0923' }).join() !== 'Lucas Fontana') throw new Error('telefone da mãe');
  location.hash = 'pacientes';
  var campo = new El('busca'); campo.value = 'joao';
  __handlers.input[1]({ target: { closest: function (s) { return s === 'input[data-pac-busca]' ? campo : null; } } });
  tem('João Grigoletto', __els.pacResultados.innerHTML); naoTem('Ricardo Sato', __els.pacResultados.innerHTML); tem('Limpar a busca', __els.pacResultados.innerHTML);
  T.ACOES['pac-limpar'](botao('pac-limpar'));
  if (T.estado.pac.busca !== '') throw new Error('não limpou');
});
run('pacientes: filtros por grupo, profissional, origem e pendência', function () {
  var risco = filtro({ grupo: 'risco' });
  contem(risco, ['Fabrício Ventura', 'Juliana Prado', 'Marcos Vieira', 'Leandro Schmitt', 'Camila Ribas'], 'ativo em risco');
  if (risco.indexOf('André Wolff') >= 0) throw new Error('André já passou de 90 dias');
  contem(filtro({ grupo: 'inativo' }), ['André Wolff', 'Márcia Lopes'], 'alta antiga e inativos');
  contem(filtro({ grupo: 'alta' }), ['Simone Tavares', 'Sérgio Pacheco'], 'alta recente');
  // Terminou as sessões com a Ana Carolina e ainda não fez a reavaliação: o ciclo continua, e a alta é da Dra. Suellen (Luiz, 07/10/2026).
  if (filtro({ grupo: 'alta' }).indexOf('Renata Rocha') >= 0 || T.grupoDe(T.pacientePorNome('Renata Rocha')) !== 'tratamento') throw new Error('sem a reavaliação, a Renata ainda está no ciclo');
  contem(filtro({ grupo: 'avaliacao' }), ['Diego Andrade', 'Paula Rezende', 'Letícia Barros'], 'avaliação');
  contem(filtro({ grupo: 'tratamento' }), ['Ricardo Sato', 'Bruno Zanetti', 'Odete Prado'], 'em tratamento');
  contem(filtro({ prof: 'moni' }), ['Odete Prado', 'Lia Fagundes'], 'Dra. Monielle');
  // A Tatiane teve alta na reavaliação de hoje: a doutora não encaminha mais à Dra. Monielle (Luiz, 07/10/2026).
  contem(filtro({ grupo: 'alta' }), ['Tatiane Moura'], 'alta de hoje');
  if (filtro({ prof: 'moni' }).indexOf('Tatiane Moura') >= 0) throw new Error('a Tatiane não foi encaminhada');
  // Mudou de propósito: Fernando Kato (contato novo) e Eduardo Viana (vai pensar) vieram pelo Instagram.
  if (filtro({ origem: 'instagram' }).sort().join() !== 'Eduardo Viana,Fernando Kato,Thiago Bastos,Vanessa Kowalski') throw new Error('instagram: ' + filtro({ origem: 'instagram' }));
  contem(filtro({ origem: 'sem' }), ['Letícia Barros', 'Márcia Lopes'], 'sem origem');
  contem(filtro({ pend: 'telefone' }), ['Sandra Oliveira', 'Joao Grigoletto'], 'telefone inválido');
  // Mudou de propósito: Cecília e Davi Bortolini usam o mesmo celular e entram como possível duplicado.
  if (filtro({ pend: 'duplicado' }).sort().join() !== 'Cecília Bortolini,Davi Bortolini,Joao Grigoletto,João Grigoletto') throw new Error('duplicado: ' + filtro({ pend: 'duplicado' }));
  var inc = filtro({ pend: 'incompleto' });
  contem(inc, ['Letícia Barros', 'Lucas Fontana', 'Sérgio Pacheco', 'Rubens Teodoro'], 'cadastro incompleto');
  // Os termos ficam para depois (Luiz, 07/10/2026): o termo de imagem revogado da Helena não deixa mais o cadastro incompleto.
  if (inc.indexOf('Helena Brandão') >= 0) throw new Error('sem os termos, o cadastro da Helena está completo');
  if (inc.indexOf('Ricardo Sato') >= 0) throw new Error('Ricardo tem cadastro completo');
  location.hash = 'pacientes';
  T.ACOES['pac-grupo'](botao('pac-grupo', { grupo: 'risco' }));
  tem('Fabrício Ventura', __els.pacResultados.innerHTML); naoTem('Ricardo Sato', __els.pacResultados.innerHTML);
  __handlers.change[0]({ target: { matches: function (s) { return s === 'select[data-pac-filtro]'; }, value: 'ana', dataset: { pacFiltro: 'prof' } } });
  if (T.estado.pac.prof !== 'ana' || T.estado.pac.grupo !== 'risco') throw new Error('filtros não se somam');
  T.ACOES['pac-limpar'](botao('pac-limpar'));
});
run('grupos: ativo em risco quando passou a data prevista pelo ciclo, até 90 dias; depois, reativação', function () {
  function g(dias, extra) { var q = JSON.parse(JSON.stringify(T.pacientePorNome('Fabrício Ventura'))); q.nome = 'Paciente de teste'; q.prot.ultima = -dias; Object.assign(q, extra || {}); return T.grupoDe(q); }
  // Fabrício fez 3 de 6: a 4ª está prevista para 40 dias depois da 3ª (Luiz, 07/10/2026).
  if (g(15) !== 'tratamento') throw new Error('15 dias: ' + g(15));
  if (g(40) !== 'tratamento') throw new Error('40 dias: ' + g(40));
  if (g(41) !== 'risco') throw new Error('41 dias: ' + g(41));
  var q5 = function (dias) { return g(dias, { prot: { feitas: 5, total: 6, ultima: -dias, faltas: [] } }); };
  if (q5(45) !== 'tratamento' || q5(46) !== 'risco') throw new Error('entre a 5ª e a 6ª, 45 dias: ' + q5(45) + ', ' + q5(46));
  if (g(48) !== 'risco') throw new Error('48 dias: ' + g(48));
  if (g(90) !== 'risco') throw new Error('90 dias: ' + g(90));
  if (g(91) !== 'inativo') throw new Error('91 dias: ' + g(91));
  if (g(120, { enc: -3 }) !== 'tratamento') throw new Error('encaminhado à médica continua em aberto');
  var f = T.estado.fila.filter(function (c) { return c.id === 'fabricio'; })[0];
  if (!f || f.prioridade !== 'risco' || f.etiqueta[1] !== 'Em risco · 48 dias') throw new Error('Fabrício fora da lista de ativos em risco');
  location.hash = 'copiloto'; T.ACOES.filtrar(botao('filtrar', { atrib: 'acompanhamento' })); tem('Fabrício Ventura'); tem('Em risco · 48 dias'); T.ACOES.filtrar(botao('filtrar', { atrib: '' }));
  location.hash = 'configuracoes'; tem('passou a data prevista da próxima sessão pelo padrão do ciclo e nada marcado'); naoTem('15 a 90 dias'); tem('Grupos de pacientes'); tem('reativação');
  location.hash = 'atribuicoes'; naoTem('Pós-MMP'); tem('Cuidados depois da sessão, fim de ciclo, home care e retornos'); tem('ativos em risco (passou a data prevista da próxima sessão e nada marcado, até 90 dias)'); tem('Parado há mais de 90 dias vai para a reativação'); naoTem('Pacientes em risco');
});
run('ficha: abre de qualquer nome, na visão da Fer', function () {
  location.hash = 'copiloto';
  T.ACOES['abrir-grupo'](botao('abrir-grupo', { gid: 'acompanhamento' })); tem('data-acao="ficha" data-nome="Juliana Prado"'); T.ACOES['abrir-grupo'](botao('abrir-grupo', { gid: '' }));
  T.ACOES.ficha(botao('ficha', { nome: 'Ricardo Sato' }));
  var h = painel();
  // A Fer vê a ficha inteira: cadastro, financeiro, produtos e fotos (Luiz, 07/10/2026).
  ['Contato e preferências', 'Origem e indicação', 'Sessões e consultas', 'Agenda', 'Home care', 'Autorização de publicação', 'Pagamentos', 'Contatos e tarefas', 'Fotos de evolução'].forEach(function (s) { tem(s, h); });
  tem('SF-0412', h); tem('Em tratamento', h); tem('Próxima:', h); tem('Copiar o celular', h); naoTem('Abrir a conversa', h); tem('Barbearia Navalha de Ouro', h); tem('Rafael mandou o contato para o WhatsApp da clínica, com a autorização da pessoa', h);
  tem('Corrigir no Clinicorp', h); tem('Prontuário não aparece no seu perfil', h); tem('Você também tem acesso a elas', h); tem('ficam registradas', h); tem('Fer abriu a ficha', h);
  naoTem('Só para consulta', h); tem('Lançar um recebimento', h); tem('R$ 2.600', h); tem('Valores fictícios', h);
  tem('2.950</strong>', h); // total recebido: avaliação e protocolo, porque o home care vem no pacote (Luiz, 07/10/2026)
  // Sem código de parceira e sem relato registrado (Luiz, 07/10/2026).
  naoTem('NAVALHA', h); naoTem('Alertas', h); naoTem('Passado à Dra. Monielle', h); naoTem('ardência', h);
  // um nome que só preenche a agenda abre uma ficha de exemplo, com "Voltar" para a ficha anterior
  var filler = T.estado.agenda.filter(function (a) { return a.data === T.K[1] && /\d$/.test(a.id); })[0];
  T.ACOES.ficha(botao('ficha', { nome: filler.nome }));
  tem('Ficha de exemplo', painel()); tem('Voltar: ficha de Ricardo', painel());
  T.ACOES['ficha-voltar'](botao('ficha-voltar'));
  tem('SF-0412', painel());
  T.ACOES.fechar();
  T.ACOES['ir-data'](botao('ir-data', { data: T.K[0] })); tem('data-acao="ficha" data-nome="Thiago Bastos"'); tem('data-acao="ficha" data-nome="Odete Prado"');
  T.ACOES.recepcao(botao('recepcao')); tem('data-acao="ficha" data-nome="Letícia Barros"', painel());
  T.ACOES.ficha(botao('ficha', { nome: 'Letícia Barros' })); tem('Voltar: recepção de hoje', painel()); tem('Sem origem', painel());
  T.ACOES['ficha-voltar'](botao('ficha-voltar')); tem('Recepção de hoje', painel());
  T.ACOES.confirmacoes(botao('confirmacoes')); tem('data-acao="ficha" data-nome="Larissa Menezes"', painel());
  T.ACOES['abrir-vaga'](botao('abrir-vaga', { id: 'v6' })); tem('data-acao="ficha" data-nome="Otávio Reis"', painel());
  T.ACOES.fechar();
  location.hash = 'pacientes'; tem('data-acao="ficha" data-cod="SF-0518"');
  location.hash = 'feito'; location.hash = 'copiloto';
});
run('ficha: Clinicorp até o corte, o resto se registra aqui, tudo com registro', function () {
  T.ACOES.ficha(botao('ficha', { nome: 'Lucas Fontana' }));
  var p = T.pacientePorNome('Lucas Fontana');
  tem('Sílvia Fontana', painel()); tem('Menor de idade', painel()); tem('Copiar o celular da mãe', painel()); tem('Cadastro incompleto', painel()); tem('Já corrigi', painel());
  // Os termos de tratamento e de uso de imagem ficam para depois (Luiz, 07/10/2026): na ficha, só a autorização de publicação.
  naoTem('Termo pendente', painel()); tem('Publicação de post', painel());
  T.ACOES['ficha-set'](botao('ficha-set', { campo: 'whats', valor: 'sim' }));
  T.ACOES['ficha-set'](botao('ficha-set', { campo: 'melhor', valor: 'tarde' }));
  if (p.whats !== 0 || p.melhor !== 'tarde') throw new Error('não registrou');
  var reg = T.estado.auditoria.filter(function (x) { return x.cod === p.cod; });
  if (reg[0].texto !== 'abriu a ficha') throw new Error('abertura não registrada');
  var mud = reg.filter(function (x) { return /melhor horário/.test(x.texto); })[0];
  if (!mud || mud.antes !== 'vazio' || mud.quem !== 'Fer') throw new Error('alteração sem quem e sem o valor antigo');
  tem('antes: vazio', painel());
  var a = T.estado.agenda.filter(function (x) { return x.nome === 'Lucas Fontana' && x.data === T.K[0]; })[0];
  if (a.faltam.length) throw new Error('o WhatsApp confirmado na ficha chega à recepção: ' + a.faltam);
  T.ACOES['ficha-corrigir'](botao('ficha-corrigir', { campo: 'cel' }));
  tem('a próxima leitura confirma', painel());
  if (!p.corrigir.cel) throw new Error('correção não anotada');
  T.ACOES['ficha-set'](botao('ficha-set', { campo: 'naoQuer', valor: 'sim' }));
  tem('Não quer contato', painel()); naoTem('Copiar o celular da mãe', painel());
  T.ACOES.fechar();
  // Não quer contato: as tarefas saem da lista e a pessoa sai das sugestões de horário vago.
  T.ACOES.ficha(botao('ficha', { nome: 'Marcos Vieira' }));
  T.ACOES['ficha-set'](botao('ficha-set', { campo: 'naoQuer', valor: 'sim' }));
  T.ACOES.fechar();
  if (T.estado.fila.filter(function (c) { return c.id === 'marcos'; })[0].status !== 'pulada') throw new Error('a tarefa do Marcos continuou na lista');
  T.ACOES['abrir-vaga'](botao('abrir-vaga', { id: 'v2' })); naoTem('Marcos Vieira', painel()); T.ACOES.fechar();
});
run('chegada: o copiloto pergunta e só "Não veio" cria a falta', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  var a = T.estado.agenda.filter(function (x) { return x.id === '0-ana-0900-thiago'; })[0];
  if (a.status !== 'nao-chegou') throw new Error('estado inicial: ' + a.status);
  T.ACOES['ver-log'](botao('ver-log')); tem('Perguntei à Fer se ele veio', painel()); T.ACOES.fechar();
  tem('o Thiago não chegou para as 9h e você precisa confirmar se ele veio');
  T.ACOES.selecionar(botao('selecionar', { id: 'thiago' }));
  tem('O Thiago Bastos não chegou para as 09:00. Ele veio?'); tem('Avisou atraso'); tem('Não veio'); naoTem('Sentimos sua falta');
  T.aprovarAtual();
  if (T.estado.fila.filter(function (c) { return c.id === 'thiago'; })[0].status !== 'pendente') throw new Error('a tecla A não responde pela Fer');
  T.ACOES.presenca(botao('presenca', { r: 'atraso', agenda: a.id }));
  if (a.status !== 'atrasado') throw new Error('atraso não registrado');
  if (T.estado.fila.some(function (c) { return /^falta-/.test(c.id); })) throw new Error('atraso não pode virar falta');
  T.ACOES['rec-chegou'](botao('rec-chegou', { id: a.id })); T.ACOES['rec-registrar-chegada'](botao('rec-registrar-chegada', { id: a.id, pedidos: '' }));
  if (a.status !== 'chegou') throw new Error('não chegou depois do atraso');
  T.ACOES.reiniciar(botao('reiniciar'));
  a = T.estado.agenda.filter(function (x) { return x.id === '0-ana-0900-thiago'; })[0];
  T.ACOES.selecionar(botao('selecionar', { id: 'thiago' }));
  T.ACOES.presenca(botao('presenca', { r: 'chegou', agenda: a.id }));
  if (a.status !== 'chegou' || T.estado.fila.some(function (c) { return /^falta-/.test(c.id); })) throw new Error('chegou e mesmo assim virou falta');
  T.ACOES.reiniciar(botao('reiniciar'));
  a = T.estado.agenda.filter(function (x) { return x.id === '0-ana-0900-thiago'; })[0];
  T.ACOES.selecionar(botao('selecionar', { id: 'thiago' }));
  T.ACOES.presenca(botao('presenca', { r: 'faltou', agenda: a.id }));
  var f = T.estado.fila.filter(function (c) { return c.id === 'falta-' + a.id; })[0];
  if (a.status !== 'faltou' || !f) throw new Error('falta não criada');
  if (f.etiqueta[1] !== 'Faltou · responder até 11:00' || f.texto.indexOf('Sentimos sua falta na sessão de hoje, às 9h') < 0) throw new Error('mensagem: ' + f.texto);
  if (T.atual().id !== f.id) throw new Error('a mensagem da falta devia abrir em seguida');
  tem('o Thiago não veio às 9h e precisa de mensagem até as 11h');
  T.aprovarAtual();
  if (f.status !== 'feita') throw new Error('mensagem não aprovada');
});
run('chegada: botões na recepção e na agenda de hoje', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  T.ACOES.recepcao(botao('recepcao'));
  var h = painel();
  tem('Não chegou · confirmar', h); tem('data-r="atraso" data-agenda="0-ana-0900-thiago"', h); tem('data-r="faltou" data-agenda="0-ana-0900-thiago"', h);
  naoTem('data-r="faltou" data-agenda="0-ana-1700-roberto"', h);
  T.ACOES.presenca(botao('presenca', { r: 'faltou', agenda: '0-ana-0900-thiago' }));
  tem('Faltou', painel());
  if (!T.estado.fila.some(function (c) { return c.id === 'falta-0-ana-0900-thiago'; })) throw new Error('sem tarefa de falta');
  T.ACOES.fechar();
  T.ACOES['ir-data'](botao('ir-data', { data: T.K[0] })); tem('data-acao="rec-chegou" data-id="0-ana-1000-caio"'); tem('Faltou');
});
run('Luiz, 07/10 · relato: nada se registra no sistema; só o lembrete, na Ponte com a médica, na conversa, na ligação e na saída', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  if ('alertas' in T.estado) throw new Error('não há lista de alertas');
  location.hash = 'medica'; naoTem('Para passar agora'); naoTem('Passado à Dra. Monielle');
  location.hash = 'copiloto'; naoTem('relato passado'); naoTem('relato para passar'); naoTem('toque em Passei'); naoTem('segurou--critico');
  location.hash = 'atribuicoes';
  tem('Ponte com a médica'); tem('passe a mensagem direto para a Dra. Monielle, no mesmo dia, sem orientar'); tem('Só a regra, sem lista nem contagem');
  location.hash = 'configuracoes'; tem('Relato clínico'); tem('nada fica registrado no sistema');
  location.hash = 'copiloto';
  T.ACOES.chat(botao('chat'));
  T.ACOES['chat-sugestao'](botao('chat-sugestao', { pergunta: 'O Caio disse que está com coceira' })); __flush();
  tem('Isso parece um relato clínico', ultimaMsg()); tem('nada fica registrado aqui', ultimaMsg()); naoTem('data-acao="chat-alerta"', ultimaMsg());
  T.ACOES.fechar();
  T.ACOES['ir-data'](botao('ir-data', { data: T.K[2] })); naoTem('Relato passado'); naoTem('Em pausa');
  location.hash = 'copiloto';
});
run('configurações: fase do corte, API e cadastro completo', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  location.hash = 'configuracoes';
  tem('Até o corte: agenda oficial no Clinicorp'); tem('não será usada: este sistema substitui o Clinicorp'); naoTem('a verificar'); naoTem('a decidir');
  tem('<strong>100%</strong> de quem já veio à clínica'); tem('No ritmo de 8 por dia, termina em cerca de 8 semanas'); tem('WhatsApp confirmado e melhor horário para ligar'); naoTem('80%');
  var s = T.livresAPartir('ana', daquiA(20), 1)[0];
  T.ACOES['ir-data'](botao('ir-data', { data: s.data })); tem('Marquei no Clinicorp</button>'); tem('lida às 06:00'); naoTem('Marcar</button>');
  T.ACOES.marcar(botao('marcar', { data: s.data, hora: s.hora, prof: 'ana' })); tem('Marquei no Clinicorp', painel()); naoTem('API', painel()); T.ACOES.fechar();
  T.ACOES.fase(botao('fase'));
  if (T.estado.fase !== 'depois') throw new Error('a chave não mudou a fase');
  location.hash = 'configuracoes'; tem('Depois do corte: agenda oficial neste sistema'); tem('aria-checked="true"');
  T.ACOES['ir-data'](botao('ir-data', { data: s.data })); naoTem('Marquei no Clinicorp'); tem('Marcar</button>'); naoTem('lida às 06:00');
  T.ACOES.ficha(botao('ficha', { nome: 'Sandra Oliveira' }));
  tem('Editar', painel()); naoTem('Já corrigi', painel()); tem('Telefone inválido', painel());
  T.ACOES['ficha-editar'](botao('ficha-editar', { campo: 'cel' }));
  getEl('ficha-edit').value = '(41) 90000-2692';
  T.ACOES['ficha-salvar'](botao('ficha-salvar', { campo: 'cel' }));
  var p = T.pacientePorNome('Sandra Oliveira');
  if (p.cel !== '(41) 90000-2692') throw new Error('não salvou');
  var ult = T.estado.auditoria[T.estado.auditoria.length - 1];
  if (ult.antes !== '(41) 3300-2692') throw new Error('sem o valor antigo: ' + JSON.stringify(ult));
  if (T.pendenciasDe(p).indexOf('telefone') >= 0) throw new Error('o telefone continua inválido');
  T.ACOES.fechar();
  T.ACOES.fase(botao('fase'));
  if (T.estado.fase !== 'antes') throw new Error('não voltou');
});
run('cadastro: meta e projeção nas Atribuições e no raio-x', function () {
  location.hash = 'atribuicoes'; tem('9%'); tem('meta: 100% de quem já veio'); tem('cerca de 8 semanas');
  T.ACOES.raiox(botao('raiox')); tem('Meta: 100% dos 341 pacientes que já vieram à clínica', painel()); tem('WhatsApp confirmado', painel());
  T.ACOES['pac-ir'](botao('pac-ir', { pend: 'incompleto' }));
  if (location.hash !== '#pacientes' || T.estado.pac.pend !== 'incompleto') throw new Error('não abriu a lista de incompletos');
  tem('Letícia Barros'); naoTem('Ricardo Sato');
  T.ACOES.reiniciar(botao('reiniciar'));
});

/* ---------- Etapas 2 e 3: crescimento, um cartão por paciente, missão cadastro, agentes e coerência ---------- */
function cartaoDe(id) { return T.estado.fila.filter(function (c) { return c.id === id; })[0]; }
run('contato novo: Marcelo urgente, com prazo, resposta pronta e os horários da Dra. Suellen', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  var c = cartaoDe('marcelo');
  if (!c || c.prioridade !== 'urgente' || c.atrib !== 'agenda' || c.etiqueta[1].indexOf('Contato novo') !== 0) throw new Error('Marcelo fora de Urgente e Agenda');
  if (c.porque.join(' ').indexOf('venceu às 09:00') < 0) throw new Error('sem o prazo: ' + c.porque);
  var p = T.prazoPrimeiraResposta('07:20'); if (p.dias !== 0 || p.hora !== '09:00') throw new Error('prazo 07:20: ' + JSON.stringify(p));
  p = T.prazoPrimeiraResposta('10:15'); if (p.dias !== 0 || p.hora !== '11:15') throw new Error('prazo 10:15');
  p = T.prazoPrimeiraResposta('17:40'); if (p.dias !== 1 || p.hora !== '08:40') throw new Error('prazo 17:40: ' + JSON.stringify(p));
  p = T.prazoPrimeiraResposta('19:10'); if (p.dias !== 1 || p.hora !== '09:00') throw new Error('prazo 19:10');
  if (!/avaliação capilar com a Dra\. Suellen/.test(c.texto) || c.texto.indexOf('Tenho ') < 0) throw new Error('resposta sem a avaliação ou sem horários');
  if (/garant|promet|resultado|remédi|medicament|minoxidil|Capilare Fort/i.test(c.texto)) throw new Error('resposta com promessa ou remédio');
  if (!c.livres || c.livres.length < 2) throw new Error('sem horários livres');
  c.livres.forEach(function (s) { if (!T.gradeDoDia(s.data, 'suellen').linhas.some(function (l) { return l.hora === s.hora && l.livres > 0; })) throw new Error('horário não está livre: ' + s.data + ' ' + s.hora); });
  location.hash = 'copiloto';
  tem('o Marcelo chamou às 07:20 e espera a primeira resposta');
  T.ACOES['abrir-tarefa'](botao('abrir-tarefa', { id: 'marcelo' }));
  tem('Primeiros horários livres da Dra. Suellen para avaliação'); tem('data-acao="marcar" data-data="' + c.livres[0].data + '"'); tem('Contato novo pelo WhatsApp da clínica');
  // Ele escolheu um horário: a Fer marca no Clinicorp e registra aqui, com o nome e a avaliação já escolhidos.
  T.ACOES.marcar(botao('marcar', { data: c.livres[0].data, hora: c.livres[0].hora, prof: 'suellen', nome: 'Marcelo Arruda', tipo: 'Avaliação' }));
  tem('value="Marcelo Arruda"', painel()); tem('aria-pressed="true">Avaliação</button>', painel());
  var quem = new El('quem'); quem.value = 'Marcelo Arruda';
  T.ACOES['confirmar-marcacao'](botao('confirmar-marcacao', { data: c.livres[0].data, hora: c.livres[0].hora, prof: 'suellen' }, ctxCom({ '.painel-corpo': new El('pc'), '#f-quem': quem, '.opcao[data-grupo="tipo"][aria-pressed="true"]': opc('Avaliação', 'Avaliação') })));
  if (c.status !== 'feita') throw new Error('o contato continuou na lista depois de marcar');
  var m = T.pacientePorNome('Marcelo Arruda');
  if (T.grupoDe(m) !== 'contato' || T.situacaoDe(m).detalhe.indexOf('avaliação marcada') < 0) throw new Error('situação: ' + JSON.stringify(T.situacaoDe(m)));
});
run('contato novo: retornos de André e Fernando, cadastro na base e a ficha', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  var a = cartaoDe('andre-l'); var f = cartaoDe('fernando');
  [a, f].forEach(function (c) { if (!c || c.atrib !== 'agenda' || c.etiqueta[1] !== 'Contato novo · retorno') throw new Error('retorno de contato novo: ' + (c && c.id)); });
  if (a.porque.join(' ').indexOf('retorno de 1 dia') < 0 || f.porque.join(' ').indexOf('retorno de 3 dias') < 0) throw new Error('retornos de 1 e 3 dias');
  // A parceira manda o contato do cliente direto para o WhatsApp da clínica, com a autorização dele: sem código e sem “Vim pela” (Luiz, 07/10/2026).
  if (a.contexto.indexOf('A Lina Costa mandou o contato ontem') < 0 || a.porque.join(' ').indexOf('Você agradeceu a ela e convidou o André para a avaliação') < 0) throw new Error('André: a Lina Costa mandou o contato');
  if (filtro({ grupo: 'contato' }).sort().join() !== 'André Lacerda,Fernando Kato,Marcelo Arruda') throw new Error('grupo contato: ' + filtro({ grupo: 'contato' }));
  location.hash = 'pacientes'; tem('Contato novo<span class="n">3</span>'); tem('Registrar contato novo');
  T.ACOES.ficha(botao('ficha', { nome: 'André Lacerda' }));
  var h = painel();
  ['Contato novo', 'Lina Costa Visagismo', 'Lina mandou o contato para o WhatsApp da clínica, com a autorização da pessoa', 'ainda não está no Clinicorp', 'Registrado aqui', 'ainda não fez a avaliação', 'chamou em'].forEach(function (t) { tem(t, h); });
  naoTem('Corrigir no Clinicorp', h); naoTem('Termo pendente', h); naoTem('LINACOSTA', h); naoTem('Chegou pelo código', h);
  T.ACOES.fechar();
  T.ACOES.ficha(botao('ficha', { nome: 'Fernando Kato' })); tem('Vim pelo Instagram', painel()); T.ACOES.fechar();
  location.hash = 'atribuicoes'; tem('3 contatos novos para responder'); tem('contato novo em até 1 hora');
  location.hash = 'configuracoes'; tem('primeira resposta em até 1 hora em horário comercial (fictício e configurável)'); tem('retornos em 1, 3 e 7 dias');
});
run('Luiz, 07/10 · contato novo: indicado por uma parceira, a Fer agradece a parceira e convida a pessoa; sem QR Code e sem “Vim pela”', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  location.hash = 'pacientes';
  T.ACOES['contato-novo'](botao('contato-novo'));
  tem('Como chegou?', painel()); tem('Indicado por uma parceira', painel()); tem('Se uma parceira mandou o contato', painel()); tem('Registrar e preparar a resposta', painel());
  naoTem('Vim pela', painel()); naoTem('Código de parceira', painel());
  var nome = new El('n'); nome.value = 'Teste Contato Fictício';
  var ruim = new El('c0'); ruim.value = '(41) 3333-1234';
  T.ACOES['registrar-contato'](botao('registrar-contato', {}, ctxCom({ '#c-nome': nome, '#c-cel': ruim, '.opcao[data-grupo="c-origem"][aria-pressed="true"]': opc('google', 'Google') })));
  if (T.estado.pacientes.some(function (p) { return p.nome === 'Teste Contato Fictício'; })) throw new Error('registrou com celular fixo');
  var cel = new El('c'); cel.value = '(41) 98888-1234';
  var hora = new El('h'); hora.value = '09:20';
  var parceira = function (extra) { var m = { '#c-nome': nome, '#c-cel': cel, '#c-hora': hora, '.opcao[data-grupo="c-origem"][aria-pressed="true"]': opc('parceira', 'Indicado por uma parceira') }; return ctxCom(Object.assign(m, extra || {})); };
  T.ACOES['registrar-contato'](botao('registrar-contato', {}, parceira()));
  if (T.estado.pacientes.some(function (p) { return p.nome === 'Teste Contato Fictício'; })) throw new Error('registrou sem dizer qual parceira');
  var parc = new El('p'); parc.value = 'linacosta';
  T.ACOES['registrar-contato'](botao('registrar-contato', {}, parceira({ '#c-parc': parc })));
  var p = T.pacientePorNome('Teste Contato Fictício');
  if (!p || T.grupoDe(p) !== 'contato' || !p.origem || p.origem.parc !== 'linacosta' || !p.origem.mandou) throw new Error('contato não entrou na base: ' + JSON.stringify(p && p.origem));
  var c = T.estado.fila[0];
  if (c.nome !== 'Teste Contato Fictício' || c.prioridade !== 'urgente' || c.etiqueta[1] !== 'Indicação · convidar até 10:20') throw new Error('cartão: ' + JSON.stringify(c.etiqueta));
  if (T.atual() !== c || location.hash !== '#copiloto') throw new Error('o convite devia abrir na lista');
  tem('Lina, da Lina Costa Visagismo, mandou o contato do Teste às 09:20, e falta o convite'); naoTem('o Teste chamou às');
  // O convite abre dizendo quem passou o contato, explica a avaliação e já leva os horários e o pedido dos dados do exame.
  if (c.texto.indexOf('Oi, Teste! Aqui é a Fer, da clínica da Dra. Suellen Ferreira. Lina, da Lina Costa Visagismo, passou o seu contato para a gente.') !== 0) throw new Error('convite: ' + c.texto);
  ['avaliação capilar com a Dra. Suellen', 'nome completo, CPF, data de nascimento, endereço e e-mail', 'Tenho '].forEach(function (t) { if (c.texto.indexOf(t) < 0) throw new Error('convite sem: ' + t); });
  if (/Vim pela|wa\.me|QR/.test(c.texto)) throw new Error('sem QR Code e sem “Vim pela”');
  // O agradecimento à parceira vai sem o nome da pessoa: para a parceira, só números gerais.
  var ag = cartaoDe('agradecer-' + p.cod);
  if (!ag || ag.texto !== 'Oi, Lina! Obrigada pela indicação. A gente entra em contato ainda hoje.' || ag.atrib !== 'agenda') throw new Error('agradecimento: ' + (ag && ag.texto));
  if (ag.porque.join(' ').indexOf('a comissão conta pelos contatos que cada parceira mandou') < 0) throw new Error('a comissão conta pelos contatos');
  tem('Agradecer a parceira');
  T.ACOES.selecionar(botao('selecionar', { id: ag.id })); T.aprovarAtual();
  if (ag.status !== 'feita') throw new Error('o agradecimento não saiu');
  if (!T.estado.log.some(function (l) { return /Lina, da Lina Costa Visagismo, mandou o contato de Teste Contato Fictício/.test(l[1]); })) throw new Error('registro do contato');
});
run('faltou à avaliação: a mensagem da falta e, depois, os retornos de contato novo', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  T.ACOES.presenca(botao('presenca', { r: 'faltou', agenda: '0-suellen-1400-letícia' }));
  var f = cartaoDe('falta-0-suellen-1400-letícia');
  if (!f || f.texto.indexOf('Sentimos sua falta na avaliação de hoje') < 0) throw new Error('mensagem da falta: ' + (f && f.texto));
  if (f.porque.join(' ').indexOf('retornos de contato novo') < 0) throw new Error('porquê sem os retornos');
  T.ACOES.selecionar(botao('selecionar', { id: f.id }));
  T.aprovarAtual();
  var p = T.pacientePorNome('Letícia Barros');
  if (!p.contato || p.contato.sit !== 'faltou à avaliação' || T.grupoDe(p) !== 'contato') throw new Error('Letícia não virou contato novo: ' + T.grupoDe(p));
  if (!T.estado.log.some(function (l) { return /faltou à avaliação/.test(l[1]) && /1, 3 e 7 dias/.test(l[1]); })) throw new Error('sem o registro dos retornos');
});
run('vai pensar com motivo: Carla (2 dias, com o resumo do plano) e Eduardo Viana (90 dias, sem desconto)', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  var c = cartaoDe('carla');
  if (!c || c.texto.indexOf('decidir com calma, em casa') < 0 || c.texto.indexOf('6 sessões com a Ana Carolina') < 0 || c.texto.indexOf('R$ 2.600') < 0) throw new Error('retorno da Carla sem o motivo ou sem o resumo');
  if (/garant|promet|resultado|desconto/i.test(c.texto)) throw new Error('promessa ou desconto no texto da Carla');
  if (c.porque.join(' ').indexOf('2, 7, 30 e 90 dias') < 0) throw new Error('regra dos retornos');
  var e = cartaoDe('eduardov');
  if (!e || e.tipo !== 'ligacao' || e.etiqueta[1] !== 'Vai pensar · 90 dias' || e.porque.join(' ').indexOf('valor') < 0) throw new Error('retorno de 90 dias do Eduardo');
  if (/desconto de|\d+ ?%|promo/i.test(e.roteiro.join(' ') + e.seguimento)) throw new Error('oferece desconto: ' + e.roteiro.join(' / '));
  if (e.nota.indexOf('Sem desconto e sem pressão') < 0) throw new Error('sem o aviso de pressão');
  T.ACOES.selecionar(botao('selecionar', { id: 'eduardov' }));
  T.ACOES.desfecho(botao('desfecho', { r: 'agora-nao' }, ctxCom({ '.sub': new El('sub') })));
  if (e.status !== 'feita' || e.resultado.texto.indexOf('sem insistir') < 0) throw new Error('agora não: ' + e.status);
  if (filtro({ grupo: 'avaliacao' }).indexOf('Carla Mendes') < 0 || T.situacaoDe(T.pacientePorNome('Carla Mendes')).detalhe !== 'avaliou e vai pensar: decidir em casa') throw new Error('Carla na tela Pacientes');
  location.hash = 'configuracoes'; tem('retornos em 2, 7, 30 e 90 dias, com o motivo'); naoTem('retornos em 2, 7 e 30 dias');
  // Na saída da avaliação, a Fer marca o motivo que a doutora deu.
  T.ACOES['rec-saiu'](botao('rec-saiu', { id: '0-suellen-0900-beatriz' })); tem('Decidir em casa', painel()); tem('2, 7, 30 e 90 dias', painel());
  var ctx = ctxCom({ '.painel-corpo': new El('pc'), '.opcao[data-grupo="fechou"][aria-pressed="true"]': opc('pensar', 'Vai pensar'), '.opcao[data-grupo="motivo"][aria-pressed="true"]': opc('tempo', 'Tempo') });
  T.ACOES['rec-registrar-saida'](botao('rec-registrar-saida', { id: '0-suellen-0900-beatriz' }, ctx));
  var b = T.estado.agenda.filter(function (a) { return a.nome === 'Beatriz Portela'; })[0];
  if (b.nota !== 'Vai pensar (tempo) · retornos em 2, 7, 30 e 90 dias') throw new Error('nota: ' + b.nota);
  if (T.pacientePorNome('Beatriz Portela').pensar.motivo !== 'tempo') throw new Error('o motivo não ficou na ficha');
  T.ACOES.fechar();
});
run('Luiz, 07/10 · reavaliação: antes de ir embora, o QR Code do Google ou o link; as fotos do antes e agora vão hoje, sem o nome e sem dizer se melhorou', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  // O Google saiu da alta e da pesquisa: vai no dia da reavaliação, para todo paciente (Luiz, 07/10/2026).
  if (cartaoDe('jorge') || cartaoDe('cristiane') || T.estado.fila.some(function (c) { return c.tipo === 'pesquisa' || /^pesquisa-/.test(c.id); })) throw new Error('sem convite de indicação, sem pesquisa e sem pedido do Google fora da reavaliação');
  var id = '0-suellen-1000-natália';
  function saida(google) {
    T.ACOES['rec-chegou'](botao('rec-chegou', { id: id }));
    T.ACOES['rec-registrar-chegada'](botao('rec-registrar-chegada', { id: id, pedidos: '' }));
    T.ACOES['rec-saiu'](botao('rec-saiu', { id: id }));
    var s = T.livresAPartir('suellen', daquiA(30), 1)[0];
    var m = { '.painel-corpo': new El('pc'), '.opcao[data-grupo="proxima"][aria-pressed="true"]': opc(s.data + '|' + s.hora, 'x') };
    if (google) m['.opcao[data-grupo="google"][aria-pressed="true"]'] = opc(google, google);
    T.ACOES['rec-registrar-saida'](botao('rec-registrar-saida', { id: id }, ctxCom(m)));
  }
  T.ACOES['rec-chegou'](botao('rec-chegou', { id: id }));
  T.ACOES['rec-registrar-chegada'](botao('rec-registrar-chegada', { id: id, pedidos: '' }));
  T.ACOES['rec-saiu'](botao('rec-saiu', { id: id }));
  ['Antes e agora e avaliação no Google', 'Mostrei o QR Code', 'Mando o link junto com as fotos', 'sem filtrar por satisfação e sem brinde', 'sem o nome na imagem'].forEach(function (t) { tem(t, painel()); });
  T.ACOES.fechar();
  saida(null);
  if (itemDe(id).status === 'saiu') throw new Error('saiu sem o QR Code ou o link do Google');
  saida('link');
  if (itemDe(id).status !== 'saiu') throw new Error('saída não registrada');
  T.ACOES.fechar();
  var f = cartaoDe('fotos-' + id);
  if (!f || f.tipo !== 'mensagem' || f.atrib !== 'acompanhamento' || f.canal !== 'WhatsApp') throw new Error('as fotos do antes e agora na lista');
  if (f.texto.indexOf('seguem as fotos do seu antes e agora, sem o seu nome na imagem') < 0 || f.texto.indexOf('https://') < 0 || f.texto.indexOf('se preferir não, tudo bem') < 0) throw new Error('fotos e link: ' + f.texto);
  if (/melhor|evolu|result|garant|brinde|desconto|cresc/i.test(f.texto.replace(/https?:\/\/\S+/g, ''))) throw new Error('a mensagem não diz se melhorou e não oferece brinde: ' + f.texto);
  if (T.conferirPedido(f, f.texto).length) throw new Error('a conferência barrou: ' + T.conferirPedido(f, f.texto));
  var p = T.pacientePorNome('Natália Freitas');
  if (!p.google || p.google.pedido !== 0 || p.google.como !== 'link') throw new Error('Google na ficha: ' + JSON.stringify(p.google));
  T.ACOES.ficha(botao('ficha', { nome: 'Natália Freitas' })); tem('Avaliação no Google', painel()); tem('Pedida hoje', painel()); tem('link junto com as fotos', painel()); T.ACOES.fechar();
  location.hash = 'copiloto'; T.ACOES.selecionar(botao('selecionar', { id: f.id })); tem('Antes e agora · mandar hoje'); tem('sem o nome do paciente'); tem('#enviar/natalia'); tem('abrir a estação para exportar e mandar'); // 13/10: a estação de fotos
  T.aprovarAtual();
  if (f.status !== 'feita') throw new Error('as fotos não saíram');
  // Com o QR Code mostrado na recepção, a mensagem das fotos vai sem o link.
  T.ACOES.reiniciar(botao('reiniciar'));
  saida('qr');
  T.ACOES.fechar();
  var f2 = cartaoDe('fotos-' + id);
  if (!f2 || f2.texto.indexOf('https://') >= 0 || T.pacientePorNome('Natália Freitas').google.como !== 'qr') throw new Error('QR Code: ' + (f2 && f2.texto));
  tem('QR Code do Google mostrado', __els.toast.innerHTML);
  location.hash = 'configuracoes'; tem('no dia da reavaliação, antes de o paciente ir embora'); tem('Antes e agora');
  location.hash = 'copiloto';
});
run('Luiz, 07/10 · baixa da sessão: a Fer registra o que a Ana Carolina passou; na última do ciclo, a próxima é a reavaliação com a Dra. Suellen', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  T.ACOES.recepcao(botao('recepcao')); tem('Fim do ciclo: marcar a reavaliação na saída', painel()); naoTem('pesquisa', painel()); T.ACOES.fechar();
  var id = '0-ana-1700-roberto';
  function abrir() {
    T.ACOES['rec-chegou'](botao('rec-chegou', { id: id }));
    T.ACOES['rec-registrar-chegada'](botao('rec-registrar-chegada', { id: id, pedidos: '' }));
    T.ACOES['rec-saiu'](botao('rec-saiu', { id: id }));
    return painel();
  }
  var h = abrir();
  ['O que a Ana Carolina passou da sessão', 'Os procedimentos do pacote vêm marcados', 'Insumos do kit padrão, para a baixa', 'Reavaliação com a Dra. Suellen, que fecha o ciclo', 'Última sessão do ciclo: a próxima é a reavaliação com a Dra. Suellen, que dá a alta', 'Em 45 dias · padrão da clínica'].forEach(function (t) { tem(t, h); });
  // Sessão 6 de 6: fototerapia e MMP, pelo pacote; o kit do MMP vem marcado, e a intradermoterapia não.
  tem('data-grupo="proc" data-valor="foto" aria-pressed="true"', h); tem('data-grupo="proc" data-valor="mmp" aria-pressed="true"', h); tem('data-grupo="proc" data-valor="intra" aria-pressed="false"', h);
  tem('data-insumo="cartucho" value="1"', h); tem('data-insumo="luvas" value="2"', h); naoTem('data-insumo="seringa"', h);
  naoTem('pesquisa de uma pergunta', h); naoTem('Ligação depois', h);
  var m = /data-grupo="proxima" data-valor="(\d{4}-\d{2}-\d{2})\|(\d{2}:\d{2})"/.exec(h);
  if (!m) throw new Error('os horários da reavaliação já vêm na tela');
  var qtd = function (i) { return T.estado.insumos.filter(function (x) { return x.id === i; })[0].qtd; };
  var antes = { cartucho: qtd('cartucho'), luvas: qtd('luvas'), seringa: qtd('seringa') };
  // A Ana Carolina usou um cartucho a mais: a Fer ajusta antes de registrar.
  var ins = function (i, v) { var e = new El(i); e.dataset = { insumo: i }; e.value = v; return e; };
  var proc = function (v, on) { var e = opc(v, v); e.setAttribute('aria-pressed', on ? 'true' : 'false'); return e; };
  T.ACOES['rec-registrar-saida'](botao('rec-registrar-saida', { id: id }, ctxCom({ '.painel-corpo': new El('pc'), '.opcao[data-grupo="proxima"][aria-pressed="true"]': opc(m[1] + '|' + m[2], 'x'),
    'TODOS .opcao[data-grupo="proc"]': [proc('foto', true), proc('mmp', true), proc('intra', false)], 'TODOS input[data-insumo]': [ins('cartucho', '2'), ins('luvas', '2')] })));
  var a = itemDe(id);
  if (a.status !== 'saiu' || a.baixa.procs.join() !== 'foto,mmp') throw new Error('baixa: ' + JSON.stringify(a.baixa));
  if (qtd('cartucho') !== antes.cartucho - 2 || qtd('luvas') !== antes.luvas - 2 || qtd('seringa') !== antes.seringa) throw new Error('a baixa usa o que a Fer ajustou');
  if (!T.estado.agenda.some(function (x) { return x.nome === 'Roberto Albuquerque' && x.prof === 'suellen' && x.proc === 'Reavaliação' && x.data === m[1]; })) throw new Error('a reavaliação com a Dra. Suellen não foi marcada');
  tem('baixa da sessão: fototerapia e MMP', __els.toast.innerHTML); tem('reavaliação com a Dra. Suellen em', __els.toast.innerHTML);
  // Sem ligação depois da sessão: só a mensagem dos cuidados (Luiz, 07/10/2026).
  if (T.estado.fila.some(function (c) { return c.nome === 'Roberto Albuquerque' && c.status === 'pendente' && c.tipo === 'ligacao'; })) throw new Error('sem ligação depois da sessão');
  if (!cartaoDe('cuidados-' + id)) throw new Error('os cuidados depois da sessão');
  var cod = T.pacientePorNome('Roberto Albuquerque').cod;
  if (!T.estado.auditoria.some(function (x) { return x.cod === cod && /deu a baixa da sessão que a Ana Carolina passou: fototerapia e MMP/.test(x.texto); })) throw new Error('a baixa fica no registro da ficha');
  T.ACOES.fechar();
  // Sem procedimento marcado, não registra.
  T.ACOES.reiniciar(botao('reiniciar'));
  abrir();
  T.ACOES['rec-registrar-saida'](botao('rec-registrar-saida', { id: id }, ctxCom({ '.painel-corpo': new El('pc'), '.opcao[data-grupo="proxima"][aria-pressed="true"]': opc('nenhuma', 'Não quis marcar agora'),
    'TODOS .opcao[data-grupo="proc"]': [proc('foto', false), proc('mmp', false), proc('intra', false)] })));
  if (itemDe(id).status === 'saiu') throw new Error('saiu sem os procedimentos da sessão');
  T.ACOES.fechar();
});
run('um cartão por paciente: Rubens com três motivos numa ligação só; Fabrício com dois numa mensagem', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  var dele = T.estado.fila.filter(function (c) { return c.status === 'pendente' && c.nome === 'Rubens Teodoro'; });
  if (dele.length !== 1) throw new Error('Rubens tem ' + dele.length + ' cartões');
  var r = dele[0];
  if (r.tipo !== 'ligacao' || r.motivos.length !== 3) throw new Error('cartão único: ' + r.tipo);
  var roteiro = r.roteiro.join(' ');
  ['47 dias', 'Capilare Tonic', 'Ana Carolina entrega', 'melhor horário'].forEach(function (t) { if (roteiro.indexOf(t) < 0) throw new Error('o roteiro não cobre: ' + t); });
  if (/termo/i.test(roteiro)) throw new Error('os termos ficam para depois: ' + roteiro);
  if (r.motivos[2].origem.indexOf('agente de cadastro') < 0) throw new Error('origem do motivo de cadastro');
  if (['acompanhamento', 'homecare', 'cadastro'].some(function (a) { return r.atribs.indexOf(a) < 0; })) throw new Error('áreas: ' + r.atribs);
  // O cartão aparece no filtro de cada área dos motivos, e a área avisa quantos estão em outra.
  location.hash = 'copiloto';
  T.ACOES.filtrar(botao('filtrar', { atrib: 'homecare' })); tem('Rubens Teodoro'); tem('Fabrício Ventura');
  T.ACOES.filtrar(botao('filtrar', { atrib: 'cadastro' })); tem('Rubens Teodoro');
  T.ACOES.filtrar(botao('filtrar', { atrib: '' })); // desde 09/10/2026, o cartão de vários motivos aparece no grupo de cada motivo
  T.ACOES['abrir-grupo'](botao('abrir-grupo', { gid: 'recepcao' })); tem('3 motivos'); tem('Rubens Teodoro'); // no notebook, o grupo Recepção junta o home care de outras áreas
  T.ACOES['abrir-tarefa'](botao('abrir-tarefa', { id: r.id })); tem('Três motivos, um cartão só'); tem('Achado pelo agente de cadastro');
  // Remarcou: o melhor horário e a reposição resolvem os outros motivos na mesma ligação.
  var sub = new El('sub');
  T.ACOES.desfecho(botao('desfecho', { r: 'remarcou' }, ctxCom({ '.sub': sub })));
  tem('Melhor horário para ligar', sub.innerHTML); tem('Home care, que a Ana Carolina entrega', sub.innerHTML);
  var alt = r.opcoesRemarcar[0];
  T.ACOES['concluir-desfecho'](botao('concluir-desfecho', { r: 'remarcou' }, ctxCom({ '.opcao[data-grupo="slot"][aria-pressed="true"]': opc(alt[0], alt[1]), '.opcao[data-grupo="melhor-lig"][aria-pressed="true"]': opc('tarde', 'Tarde'), '.opcao[data-grupo="repor"][aria-pressed="true"]': opc('sim', 'Entregar na sessão') })));
  var p = T.pacientePorNome('Rubens Teodoro');
  if (r.status !== 'feita' || p.melhor !== 'tarde' || r.resultado.texto.indexOf('home care na sessão, com a Ana Carolina') < 0) throw new Error('desfecho: ' + (r.resultado && r.resultado.texto));
  var d = alt[0].split('|');
  if (!T.estado.agenda.some(function (a) { return a.nome === 'Rubens Teodoro' && a.data === d[0] && a.hora === d[1] && a.proc === 'Sessão 4 de 6'; })) throw new Error('sessão não marcada');
  var f = cartaoDe('fabricio');
  if (f.tipo !== 'mensagem' || f.motivos.length !== 2 || f.texto.indexOf('Capilare Tonic deve ter acabado') < 0 || f.texto.indexOf('A Ana Carolina entrega para você') < 0 || f.texto.indexOf('faltam 3 sessões') < 0) throw new Error('Fabrício: ' + f.texto);
  if ((f.texto.match(/Oi, Fabrício/g) || []).length !== 1) throw new Error('saudação repetida');
  // O editor, em código: os cuidados depois da sessão ficam à parte; o resto se junta, com a prioridade mais alta.
  var itens = [
    { id: 'a', tipo: 'mensagem', nome: 'Paciente X', prioridade: 'retorno', atrib: 'homecare', etiqueta: ['info', 'Home care', null], porque: ['a'], trecho: 'Reposição.', status: 'pendente' },
    { id: 'b', tipo: 'mensagem', nome: 'Paciente X', prioridade: 'tratamento', atrib: 'acompanhamento', etiqueta: ['info', 'Cuidados depois da sessão', null], porque: ['b'], texto: 'Seguem os cuidados.', anexos: ['mmp'], separado: true, status: 'pendente' },
    { id: 'c', tipo: 'ligacao', nome: 'Paciente X', prioridade: 'risco', atrib: 'acompanhamento', etiqueta: ['atencao', 'Em risco', null], porque: ['c'], passos: ['Ligar.'], trecho: 'Risco.', status: 'pendente' },
  ];
  var junto = T.juntarPorPaciente(itens);
  if (junto.length !== 2) throw new Error('devia sobrar os cuidados à parte e um cartão com os outros dois: ' + junto.length);
  if (!junto.some(function (c) { return c.separado && c.id === 'b'; }) || !junto.some(function (c) { return c.motivos && c.motivos.length === 2 && c.tipo === 'ligacao' && c.id === 'c'; })) throw new Error('junção errada');
  // Um cartão que só tem o texto completo junta sem repetir a saudação e sem cortar o texto.
  var jun = T.juntarPorPaciente([
    { id: 'x1', tipo: 'mensagem', nome: 'Paciente Y', prioridade: 'tratamento', atrib: 'agenda', etiqueta: ['atencao', 'Saiu sem próxima data', null], porque: ['x'], texto: 'Oi, Paciente! Aqui é da clínica da Dra. Suellen. Hoje você saiu sem a próxima data.', status: 'pendente' },
    { id: 'x2', tipo: 'mensagem', nome: 'Paciente Y', prioridade: 'retorno', atrib: 'marketing', etiqueta: ['acento', 'Convite', null], porque: ['y'], trecho: 'Pode passar este link.', status: 'pendente' },
  ]);
  if (jun.length !== 1 || (jun[0].texto.match(/Aqui é/g) || []).length !== 1 || jun[0].texto.indexOf('\n\nHoje você saiu sem a próxima data.\n\nPode passar este link.') < 0) throw new Error('texto juntado: ' + jun[0].texto);
});
run('agentes da noite: origem em cada item e cota de 3 por dia; o resto fica para amanhã', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  var x = T.aplicarCota([{ id: 1 }, { id: 2, clinico: true }, { id: 3 }, { id: 4 }, { id: 5 }, { id: 6 }]);
  if (x.hoje.length !== 4 || x.amanha.length !== 2 || !x.hoje.some(function (i) { return i.clinico; })) throw new Error('cota: ' + JSON.stringify(x));
  if (T.COTA_AGENTES !== 3) throw new Error('cota diferente de 3');
  var seg = T.estado.segurados.map(function (s) { return s.nome; }).join();
  if (seg !== 'Débora Assis,Gustavo Lemos') throw new Error('seguradas: ' + seg);
  location.hash = 'copiloto';
  tem('O copiloto segurou 2 sugestões para amanhã.'); T.ACOES['abrir-grupo'](botao('abrir-grupo', { gid: 'acompanhamento' })); tem('Agente de retenção'); T.ACOES['abrir-grupo'](botao('abrir-grupo', { gid: '' }));
  var h = cartaoDe('helena');
  if (!h || h.origem.indexOf('Achado pelo agente de retenção') < 0) throw new Error('Helena sem a origem');
  // Dos agentes, entram hoje só 3 itens novos: Helena, a reposição do Fabrício e o cadastro do Rubens.
  var deAgentes = 0;
  T.estado.fila.forEach(function (c) { (c.itens || [c]).forEach(function (i) { if (i.agente) deAgentes += 1; }); });
  if (deAgentes !== 3) throw new Error('itens de agentes hoje: ' + deAgentes);
  T.ACOES.segurados(botao('segurados')); tem('Sugestões para amanhã', painel()); tem('Débora Assis', painel()); tem('Agente de qualidade e reputação', painel()); naoTem('Alerta clínico', painel());
  T.ACOES.trazer(botao('trazer', { id: 'debora' }));
  if (T.estado.segurados.length !== 1 || !T.estado.fila.some(function (c) { return c.nome === 'Débora Assis' && c.status === 'pendente'; })) throw new Error('não trouxe a Débora');
  tem('O copiloto segurou 1 sugestão para amanhã.');
  T.ACOES.fechar();
  location.hash = 'configuracoes'; tem('até 3 novas por dia, dentro do limite de ligações'); tem('12 (fictício, a definir com a clínica)');
  T.ACOES['ver-log'](botao('ver-log')); tem('segurei 2 para amanhã', painel()); tem('Juntei num cartão só', painel()); T.ACOES.fechar();
});
run('missão cadastro: um lote por dia, na ordem certa, com o que corrigir e onde', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  location.hash = 'copiloto';
  var m = cartaoDe('missao');
  var lote = T.missaoDe(m).map(function (p) { return p.nome; });
  if (lote.length < 5 || lote.length > 10) throw new Error('tamanho do lote: ' + lote.length);
  // Mudou de propósito em 07/10, na rodada b: sem os termos, o cadastro do Fábio Rangel está completo, e entra o André Wolff (inativo).
  if (lote.join() !== 'Vinícius Prates,Elaine Duarte,Rogério Lima,Sandra Oliveira,Davi Bortolini,Cecília Bortolini,João Grigoletto,André Wolff') throw new Error('ordem: ' + lote.join());
  // Fica de fora quem vem hoje (completa na chegada), quem nunca veio e quem já está num cartão de hoje.
  ['Lucas Fontana', 'Letícia Barros', 'Paula Rezende', 'Rubens Teodoro', 'Helena Brandão', 'Joao Grigoletto', 'Marcelo Arruda'].forEach(function (n) { if (lote.indexOf(n) >= 0) throw new Error(n + ' não devia estar no lote'); });
  var onde = function (nome) { return T.pendenciasCadastro(T.pacientePorNome(nome)); };
  if (onde('Elaine Duarte')[0].onde !== 'clinicorp' || onde('Vinícius Prates')[0].onde !== 'aqui') throw new Error('onde corrigir');
  if (!onde('Davi Bortolini').some(function (i) { return i.onde === 'raiox'; })) throw new Error('duplicado vai para o raio-x');
  T.ACOES['abrir-tarefa'](botao('abrir-tarefa', { id: 'missao' }));
  ['Lote de hoje · 0 de 8 feitos', 'No Clinicorp', 'No raio-x', 'Pedir na chegada, amanhã', 'termina em cerca de 8 semanas', 'Concluir por hoje', 'Achado pelo agente de cadastro'].forEach(function (t) { tem(t); });
  T.ACOES['missao-feito'](botao('missao-feito', { cod: T.pacientePorNome('Elaine Duarte').cod }));
  tem('Lote de hoje · 1 de 8 feitos');
  // Completar pela ficha também conta: o Vinícius informou o melhor horário.
  T.ACOES.ficha(botao('ficha', { nome: 'Vinícius Prates' })); tem('Na missão cadastro de hoje', painel());
  T.ACOES['ficha-set'](botao('ficha-set', { campo: 'melhor', valor: 'manha' })); T.ACOES.fechar();
  tem('Lote de hoje · 2 de 8 feitos');
  T.ACOES['missao-concluir'](botao('missao-concluir'));
  if (m.status !== 'feita' || m.resultado.texto.indexOf('2 de 8') < 0) throw new Error('missão: ' + m.status);
  if (T.estado.cadastroHoje !== 2 || T.metaCadastro().pct !== 10) throw new Error('a meta não andou: ' + T.metaCadastro().pct);
});
run('raio-x: telefones inválidos com o motivo e duplicados lado a lado', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  var motivo = function (n) { return T.telefoneProblema(T.pacientePorNome(n)) || ''; };
  if (motivo('Sandra Oliveira').indexOf('fixo') !== 0) throw new Error('fixo: ' + motivo('Sandra Oliveira'));
  if (motivo('Joao Grigoletto') !== 'faltam dígitos') throw new Error('faltam dígitos: ' + motivo('Joao Grigoletto'));
  if (motivo('Neusa Carvalho') !== 'em branco') throw new Error('em branco');
  if (motivo('Ivone Brenner') !== 'é o número da clínica') throw new Error('número da clínica');
  if (motivo('Davi Bortolini') !== 'repetido: é o mesmo de Cecília Bortolini') throw new Error('repetido: ' + motivo('Davi Bortolini'));
  if (motivo('Cecília Bortolini') !== '' || motivo('Ricardo Sato') !== '') throw new Error('falso positivo');
  T.ACOES.raiox(botao('raiox'));
  var h = painel();
  ['Telefones inválidos, com o motivo', 'Faltam dígitos', 'Fixo', 'Em branco', 'Número da clínica', 'Repetido', 'Corrigir no Clinicorp', 'Possíveis duplicados, lado a lado', 'Mais antigo', 'Mais novo', 'É a mesma pessoa', 'São pessoas diferentes'].forEach(function (t) { tem(t, h); });
  var pares = T.paresDuplicados().map(function (x) { return x[0].nome + ' x ' + x[1].nome; }).join();
  if (pares !== 'João Grigoletto x Joao Grigoletto,Cecília Bortolini x Davi Bortolini') throw new Error('pares: ' + pares);
  // São pessoas diferentes: o par não volta, e o celular repetido continua até a correção.
  var cec = T.pacientePorNome('Cecília Bortolini'); var davi = T.pacientePorNome('Davi Bortolini');
  T.ACOES['dup-diferentes'](botao('dup-diferentes', { par: [cec.cod, davi.cod].sort().join('|') }));
  if (T.paresDuplicados().length !== 1 || T.duplicadosDe(cec).length || T.pendenciasDe(cec).indexOf('duplicado') >= 0) throw new Error('o par voltou');
  if (motivo('Davi Bortolini').indexOf('repetido') !== 0) throw new Error('o celular repetido devia continuar até a correção');
  naoTem('Davi Bortolini</button><span class="num">', painel());
  // É a mesma pessoa: inativar o duplicado no Clinicorp, e o importador junta os históricos.
  var k = ['SF-1187', 'SF-2214'].sort().join('|');
  T.ACOES['dup-mesma'](botao('dup-mesma', { par: k }));
  tem('inative o cadastro SF-2214 no Clinicorp', painel()); tem('o importador junta os históricos no SF-1187', painel()); tem('Já inativei no Clinicorp', painel());
  T.ACOES['dup-inativei'](botao('dup-inativei', { par: k }));
  tem('Inativado no Clinicorp às', painel());
  T.ACOES.fechar();
  if (!T.estado.log.some(function (l) { return /pessoas diferentes/.test(l[1]); })) throw new Error('sem registro da decisão');
});
run('Luiz, 07/10 · a Dra. Monielle à parte: a Fer marca a primeira consulta de quem fechou, e não há selo de pausa', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  if (cartaoDe('paulom')) throw new Error('a resposta da Dra. Monielle não vira mais tarefa: ela não registra nada no sistema');
  var c = cartaoDe('otavior-moni');
  if (!c || c.atrib !== 'agenda' || c.origem.indexOf('Encaminhado pela Dra. Suellen') < 0 || c.vaga !== 'v6') throw new Error('primeira consulta do Otávio');
  if (/Capilare Fort|remédi|dose|usar/i.test(c.texto) || c.texto.indexOf('primeira consulta com a Dra. Monielle') < 0) throw new Error('mensagem: ' + c.texto);
  if (T.VAGA.v6.sugestoes.join() !== 'otavior') throw new Error('o horário livre da Dra. Monielle é para quem espera a primeira consulta');
  T.ACOES['abrir-vaga'](botao('abrir-vaga', { id: 'v6' }));
  tem('Só entra quem fechou o protocolo e espera a primeira consulta', painel()); tem('Otávio Reis', painel()); tem('A IA não decide quem precisa de consulta médica.', painel());
  T.ACOES.fechar();
  location.hash = 'copiloto';
  T.ACOES.selecionar(botao('selecionar', { id: 'otavior-moni' })); T.aprovarAtual();
  if (T.estado.vagas.v6.estado !== 'convidado' || T.estado.vagas.v6.pessoa !== 'otavior') throw new Error('o convite não foi para a vaga da médica');
  // Sem selo de pausa no sistema: a equipe combina direto com a Dra. Monielle.
  T.ACOES['ir-data'](botao('ir-data', { data: T.K[1] })); naoTem('Em pausa');
  location.hash = 'configuracoes'; naoTem('Sessões em pausa pela Dra. Monielle'); tem('processo à parte'); tem('a Fer marca a primeira consulta');
  location.hash = 'copiloto';
});
run('configurações e atribuições: as regras das etapas 2 e 3', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  location.hash = 'configuracoes';
  ['Contato novo', 'Faltou à avaliação', 'Avaliação no Google', 'Antes e agora', 'Baixa da sessão', 'Lançamento', 'Insumos', 'Um cartão por paciente', 'Sugestões dos agentes da noite', 'Missão cadastro', 'processo à parte', 'sem filtrar por satisfação e sem brinde'].forEach(function (t) { tem(t); });
  // A pesquisa do fim do ciclo e o convite de indicação saíram (Luiz, 07/10/2026).
  naoTem('Pesquisa do fim do ciclo'); naoTem('Convite de indicação');
  tem('Contato novo</span></dt>');
  location.hash = 'atribuicoes';
  tem('missão de hoje: 8 pacientes'); tem('Abrir a missão de hoje'); tem('Só a regra, sem lista nem contagem'); tem('1 pedido do dia: autorização de imagem'); tem('quem vai pensar (retornos de 2, 7, 30 e 90 dias)');
  T.ACOES.reiniciar(botao('reiniciar'));
});
/* ---------- Etapa 4: a experiência do paciente ---------- */
// O que foi copiado para o WhatsApp da clínica (no sistema, aprovar abre o WhatsApp com o texto pronto).
navigator.clipboard.writeText = function (t) { globalThis.__copiado = t; return Promise.resolve(); };
function copiado() { return globalThis.__copiado || ''; }
// Copiar não marca como enviada (Luiz, 15/10/2026): a Fer copia, cola no WhatsApp da clínica e toca em Enviei.
function copiarCartao(id) { globalThis.__copiado = ''; T.ACOES['copiar-msg'](botao('copiar-msg', { fonte: 'cartao', id: id || T.atual().id })); }
function copiarConf(id) { globalThis.__copiado = ''; T.ACOES['copiar-msg'](botao('copiar-msg', { fonte: 'conf', id: id })); }
function campo(v) { var e = new El('campo'); e.value = v; return e; }
run('etapa 4 · marcação: "Marquei no Clinicorp" prepara a mensagem com dia, horário, profissional, endereço, duração e a regra de remarcação', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  var s = T.livresAPartir('ana', daquiA(20), 1)[0];
  T.ACOES.marcar(botao('marcar', { data: s.data, hora: s.hora, prof: 'ana' }));
  T.ACOES['confirmar-marcacao'](botao('confirmar-marcacao', { data: s.data, hora: s.hora, prof: 'ana' }, ctxCom({ '.painel-corpo': new El('pc'), '#f-quem': campo('Teste Fictício'), '.opcao[data-grupo="tipo"][aria-pressed="true"]': opc('Sessão', 'Sessão') })));
  var h = painel();
  ['Mensagem de marcação', 'Registrado: Teste', 'A próxima leitura do Clinicorp confirma', 'Sua sessão com a Ana Carolina, biomédica da clínica, ficou marcada para', s.data.slice(8, 10) + '/' + s.data.slice(5, 7), 'Leva cerca de 1 hora', 'Rua Exemplo, 123, sala 4, Batel, Curitiba', 'avise com pelo menos 24 horas de antecedência', 'Enviei', 'Copiar', 'Deixar na lista'].forEach(function (t) { tem(t, h); });
  var c = T.estado.fila.filter(function (x) { return x.marcacao && x.nome === 'Teste Fictício'; })[0];
  if (!c || c.status !== 'pendente' || c.atrib !== 'agenda') throw new Error('a mensagem de marcação não ficou na lista');
  if (/garant|promet|resultado/i.test(c.texto)) throw new Error('promessa na mensagem de marcação');
  copiarCartao(c.id);
  if (copiado().indexOf('Rua Exemplo, 123') < 0) throw new Error('o texto copiado não leva o endereço');
  if (c.status !== 'pendente') throw new Error('copiar não é enviar');
  T.ACOES['enviar-cartao'](botao('enviar-cartao', { id: c.id }));
  if (c.status !== 'feita' || !__els.painel.hidden) throw new Error('não enviou pelo painel');
  // Depois do corte, "Marcar" e a mesma mensagem.
  T.ACOES.fase(botao('fase'));
  var s2 = T.livresAPartir('ana', daquiA(25), 1)[0];
  T.ACOES.marcar(botao('marcar', { data: s2.data, hora: s2.hora, prof: 'ana' }));
  T.ACOES['confirmar-marcacao'](botao('confirmar-marcacao', { data: s2.data, hora: s2.hora, prof: 'ana' }, ctxCom({ '.painel-corpo': new El('pc'), '#f-quem': campo('Teste Dois'), '.opcao[data-grupo="tipo"][aria-pressed="true"]': opc('Manutenção', 'Manutenção') })));
  tem('Marcado: Teste', painel()); tem('Sua sessão de manutenção com a Ana Carolina', painel());
  T.ACOES.fechar(); T.ACOES.fase(botao('fase'));
  if (!T.estado.fila.some(function (x) { return x.marcacao && x.nome === 'Teste Dois' && x.status === 'pendente'; })) throw new Error('fechar o painel devia deixar a mensagem na lista');
});
run('etapa 4 · marcação: o contato novo marca a primeira avaliação, e as boas-vindas vão na véspera', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  var c = cartaoDe('marcelo');
  var s = c.livres[0];
  T.ACOES['confirmar-marcacao'](botao('confirmar-marcacao', { data: s.data, hora: s.hora, prof: 'suellen' }, ctxCom({ '.painel-corpo': new El('pc'), '#f-quem': campo('Marcelo Arruda'), '.opcao[data-grupo="tipo"][aria-pressed="true"]': opc('Avaliação', 'Avaliação') })));
  if (c.status !== 'feita') throw new Error('o contato continuou na lista');
  tem('Sua avaliação capilar com a Dra. Suellen ficou marcada para', painel()); tem('Na véspera, mando as orientações para a avaliação.', painel());
  var a = T.estado.agenda.filter(function (x) { return x.nome === 'Marcelo Arruda'; })[0];
  if (a.marca !== 'Primeira vez na clínica' || !T.primeiraVez(a)) throw new Error('a primeira avaliação não ficou marcada como primeira vez');
  T.ACOES.fechar();
});
run('etapa 4 · boas-vindas: a Paula recebe as boas-vindas no pedido da véspera; o preparo só vai depois de aprovado, como está', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  var a = itemDe('1-suellen-1400-paula');
  var t = T.textoConfirmacao(a);
  ['Que bom que você vem conhecer a clínica', 'Podemos confirmar?', 'Como é a avaliação', 'O que levar: se tiver exames de sangue feitos nos últimos 90 dias, pode trazer.', 'Rua Exemplo, 123'].forEach(function (x) { if (t.indexOf(x) < 0) throw new Error('boas-vindas sem: ' + x); });
  if (/garant|promet|resultado/i.test(t)) throw new Error('promessa nas boas-vindas');
  if (T.textoAprovado('preparo') || T.faltamAprovar('preparo').join() !== 'suellen') throw new Error('o preparo devia esperar a Dra. Suellen');
  location.hash = 'copiloto'; T.ACOES.filtrar(botao('filtrar', { atrib: 'confirmacoes' }));
  tem('Boas-vindas junto'); tem('Aguardando a Dra. Suellen'); tem('não pode ser anexado'); tem('Paula Rezende vem pela primeira vez: o pedido leva as boas-vindas');
  copiarConf(a.id);
  if (a.conf !== 'a-enviar') throw new Error('copiar o pedido não é enviar');
  T.ACOES['conf-enviar'](botao('conf-enviar', { id: a.id }));
  if (a.conf !== 'enviada' || a.preparoEnviado !== false) throw new Error('pedido: ' + a.conf + ' ' + a.preparoEnviado);
  if (copiado().indexOf('Como é a avaliação') < 0 || copiado().indexOf('Preparo do cabelo para a avaliação') >= 0) throw new Error('texto sem aprovação foi junto: ' + copiado());
  // A aprovação chega do copiloto da Dra. Suellen: quem já recebeu as boas-vindas ganha o preparo, ainda na véspera.
  location.hash = 'configuracoes';
  T.ACOES['texto-demo-aprovar'](botao('texto-demo-aprovar', { id: 'preparo' }));
  if (!T.textoAprovado('preparo')) throw new Error('não aprovou');
  var p = T.estado.fila.filter(function (x) { return x.id === 'preparo-' + a.id; })[0];
  if (!p || p.status !== 'pendente' || p.anexos.join() !== 'preparo') throw new Error('sem a mensagem do preparo');
  location.hash = 'copiloto';
  T.ACOES.selecionar(botao('selecionar', { id: p.id })); tem('Vai junto, como está'); tem('Texto aprovado');
  copiarCartao(); T.aprovarAtual();
  if (p.status !== 'feita' || copiado().indexOf(T.TEXTOS_FIXOS.preparo.texto) < 0) throw new Error('o preparo não foi como está');
  // Com o texto aprovado, o pedido da véspera já leva o preparo.
  T.ACOES.reiniciar(botao('reiniciar'));
  T.ACOES['texto-demo-aprovar'](botao('texto-demo-aprovar', { id: 'preparo' }));
  a = itemDe('1-suellen-1400-paula');
  copiarConf(a.id);
  T.ACOES['conf-enviar'](botao('conf-enviar', { id: a.id }));
  if (!a.preparoEnviado || copiado().indexOf(T.TEXTOS_FIXOS.preparo.texto) < 0) throw new Error('o pedido não levou o preparo aprovado');
  if (T.estado.fila.some(function (x) { return x.id === 'preparo-' + a.id; })) throw new Error('não precisava de outra mensagem');
});
run('etapa 4 · textos fixos: só a Dra. Suellen aprova; os cuidados que a clínica ditou vão depois de toda sessão, como estão', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  location.hash = 'configuracoes';
  ['Textos fixos para pacientes', 'Preparo do cabelo para a avaliação', 'Cuidados depois da sessão', 'Dúvidas frequentes', 'Aguardando a Dra. Suellen', 'Aprovou hoje, às 09:20', 'Tem estacionamento?', 'Quais são as formas de pagamento?', 'Quanto tempo dura a sessão?', 'Simular a aprovação da Dra. Suellen', 'nunca reescreve conteúdo clínico'].forEach(function (t) { tem(t); });
  // Só a Dra. Suellen aprova; nenhum texto passa pela Dra. Monielle, e sai o texto da fototerapia (Luiz, 07/10/2026).
  naoTem('Cuidados depois da fototerapia'); naoTem('Aguardando a Dra. Monielle'); naoTem('Texto clínico: aprovam');
  if (!T.textoAprovado('mmp') || T.textoAprovado('preparo') || !T.textoAprovado('faq') || T.TEXTOS_FIXOS.foto) throw new Error('aprovações iniciais');
  if (T.TEXTOS_FIXOS.mmp.precisa.join() !== 'suellen' || T.faltamAprovar('preparo').join() !== 'suellen') throw new Error('só a Dra. Suellen aprova');
  // Os quatro cuidados que a clínica ditou, e o fecho.
  var tm = T.TEXTOS_FIXOS.mmp.texto;
  ['Não lave o cabelo nas próximas 4 horas.', 'Nas próximas 4 horas, evite atividade física que faça suar muito.', 'Química nos fios (tintura, alisamento, selagem) só 48 horas depois da sessão.', 'No dia da aplicação no consultório, não precisa usar a loção do home care.'].forEach(function (t) { if (tm.indexOf(t) < 0) throw new Error('cuidado: ' + t); });
  if (!/Qualquer dúvida, é só chamar por aqui\.$/.test(tm) || (tm.match(/^• /gm) || []).length !== 4) throw new Error('os quatro cuidados e o fecho: ' + tm);
  // Sem resposta da Fer ao relato, sai o texto “Para quem relata sintoma” (Luiz, 07/10/2026).
  if (T.TEXTOS_FIXOS.relato) throw new Error('o texto para quem relata sintoma saiu da biblioteca'); naoTem('Para quem relata sintoma');
  T.ACOES.textos(botao('textos', { id: 'mmp' })); tem('Textos fixos aprovados', painel()); tem('texto-fixo--foco', painel()); tem('2 de 3 prontos para anexar', painel());
  tem('Mensagem informativa depois de toda sessão', painel()); tem('Não pergunta como a pessoa está', painel()); T.ACOES.fechar();
  location.hash = 'copiloto';
  // Sem ligação depois do MMP (Luiz, 07/10/2026): depois de toda sessão com a Ana Carolina, só a mensagem informativa com os cuidados aprovados.
  naoTem('Pós-MMP'); naoTem('pós-MMP'); naoTem('Ligação depois do MMP');
  if (T.estado.fila.some(function (x) { return x.tipo === 'ligacao' && (x.anexos || []).indexOf('mmp') >= 0; })) throw new Error('ainda há ligação com os cuidados');
  var id = '0-ana-1500-lucas';
  T.ACOES['rec-chegou'](botao('rec-chegou', { id: id }));
  T.ACOES['rec-registrar-chegada'](botao('rec-registrar-chegada', { id: id, pedidos: '' }));
  T.ACOES['rec-saiu'](botao('rec-saiu', { id: id }));
  var s = T.livresAPartir('ana', daquiA(30), 1)[0];
  T.ACOES['rec-registrar-saida'](botao('rec-registrar-saida', { id: id }, ctxCom({ '.painel-corpo': new El('pc'), '.opcao[data-grupo="proxima"][aria-pressed="true"]': opc(s.data + '|' + s.hora, 'x'), 'TODOS .opcao[data-grupo="hc"][aria-pressed="true"]': [opc('nenhum', 'Nada hoje')] })));
  T.ACOES.fechar();
  var aviso = __els.toast.innerHTML;
  tem('os cuidados depois da sessão na sua lista', aviso); naoTem('home care', aviso);
  if (itemDe(id).homecare) throw new Error('“Nada hoje” não é home care: ' + itemDe(id).homecare);
  var c = cartaoDe('cuidados-' + id);
  if (!c || c.tipo !== 'mensagem' || c.anexos.join() !== 'mmp' || c.atrib !== 'acompanhamento') throw new Error('sem a mensagem dos cuidados: ' + JSON.stringify(c && [c.tipo, c.anexos, c.atrib]));
  if (/como (você )?está|tudo bem\?/i.test(c.texto + c.porque.join(' '))) throw new Error('a mensagem só informa, sem perguntar como a pessoa está');
  if (T.textoEnvio(c).indexOf(T.TEXTOS_FIXOS.mmp.texto) < 0) throw new Error('os cuidados não vão como estão');
  T.ACOES.selecionar(botao('selecionar', { id: c.id }));
  tem('Cuidados depois da sessão'); tem('Vai junto, como está');
  copiarCartao(); T.aprovarAtual();
  if (c.status !== 'feita' || copiado().indexOf(T.TEXTOS_FIXOS.mmp.texto) < 0) throw new Error('os cuidados não foram como estão: ' + copiado());
  // Fica num cartão à parte, mesmo quando a pessoa tem outra tarefa no dia.
  if (T.estado.fila.some(function (x) { return x.itens && x.itens.some(function (i) { return /^cuidados-/.test(i.id); }); })) throw new Error('os cuidados entraram num cartão junto');
  // O que espera a aprovação da Dra. Suellen não vai junto: o preparo.
  location.hash = 'configuracoes'; T.ACOES.textos(botao('textos', { id: 'preparo' })); tem('Aguardando a Dra. Suellen', painel()); T.ACOES.fechar();
  location.hash = 'copiloto';
});
run('etapa 4 · colar a mensagem: confirmou e relatou na mesma mensagem; o guardião lê as palavras do paciente', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  location.hash = 'copiloto'; tem('Colar mensagem de paciente');
  var l = T.lerMensagem('Oi, confirmo amanhã. Ah, fiquei com o couro cabeludo vermelho depois da última', 'Larissa Menezes');
  if (l.map(function (s) { return s.tipo; }).join() !== 'confirmou,relato' || l[1].palavra !== 'vermelho' || l[1].trecho.indexOf('couro cabeludo vermelho') < 0) throw new Error('leitura: ' + JSON.stringify(l));
  T.ACOES.colar(botao('colar'));
  tem('Colar a mensagem do paciente', painel()); tem('data-nome="Larissa Menezes"', painel()); tem('Usar o exemplo da demonstração', painel());
  T.ACOES['colar-quem'](botao('colar-quem', { nome: 'Larissa Menezes' }));
  T.ACOES['colar-ler'](botao('colar-ler', {}, ctxCom({ '#colar-texto': campo(T.EXEMPLO_COLAR.texto), '#colar-nome': campo('') })));
  var h = painel();
  ['O que eu entendi da mensagem de Larissa', 'Confirmou a sessão de amanhã, às 9h, com a Ana Carolina', 'guardiao-marca">vermelho</mark>', 'O guardião leu as palavras do próprio paciente', 'Registrar o que está marcado', 'passe a mensagem direto para a Dra. Monielle, no mesmo dia, sem orientar'].forEach(function (t) { tem(t, h); });
  if (h.split('Relato que chega na clínica').length !== 2) throw new Error('o lembrete aparece uma vez só');
  T.ACOES['colar-registrar'](botao('colar-registrar'));
  var a = itemDe('1-ana-0900-larissa');
  if (a.conf !== 'confirmou') throw new Error('não registrou a confirmação');
  // Relato não se registra no sistema (Luiz, 07/10/2026): a Fer passa a mensagem como chegou, direto para a Dra. Monielle.
  if ('alertas' in T.estado || T.estado.fila.some(function (x) { return x.nome === 'Larissa Menezes' && x.status === 'pendente' && /relato/i.test(x.etiqueta[1]); })) throw new Error('o relato não vira alerta nem tarefa');
  if (T.estado.log.some(function (l) { return /vermelho/.test(l[1]); })) throw new Error('as palavras do relato não ficam no registro');
  tem('Registrado para Larissa Menezes', painel()); tem('passe a mensagem direto para a Dra. Monielle, ainda hoje; nada fica registrado', painel()); naoTem('Abrir a resposta com o texto aprovado', painel());
  T.ACOES.ficha(botao('ficha', { nome: 'Larissa Menezes' })); tem('Mensagem colada na recepção', painel()); naoTem('vermelho', painel()); T.ACOES.fechar();
  // As outras leituras: remarcar, cancelar, dúvida com texto aprovado, remédio e exame entregue.
  if (T.lerMensagem('Não vou conseguir amanhã, dá para remarcar?')[0].tipo !== 'remarcar') throw new Error('remarcar');
  if (T.lerMensagem('Preciso cancelar a avaliação de amanhã')[0].tipo !== 'cancelou') throw new Error('cancelou');
  var d = T.lerMensagem('Oi! Tem estacionamento aí perto?');
  if (d[0].tipo !== 'duvida' || d[0].faq !== 'estacionamento') throw new Error('dúvida: ' + JSON.stringify(d));
  if (T.lerMensagem('Posso tomar o Capilare Fort junto com a vitamina?')[0].tipo !== 'remedio') throw new Error('remédio');
  if (!T.lerMensagem('Segue o resultado do exame que a doutora pediu', 'Vanessa Kowalski').some(function (s) { return s.tipo === 'exame'; })) throw new Error('exame');
  // Dúvida que não é clínica: a resposta é o texto aprovado, anexado como está.
  T.ACOES['colar-novo'](botao('colar-novo'));
  T.ACOES['colar-ler'](botao('colar-ler', {}, ctxCom({ '#colar-texto': campo('Oi! Tem estacionamento aí perto?'), '#colar-nome': campo('Igor Batista') })));
  tem('Dúvida que não é clínica', painel());
  T.ACOES['colar-registrar'](botao('colar-registrar'));
  var resp = T.estado.fila.filter(function (x) { return x.nome === 'Igor Batista' && x.anexos && x.anexos[0] === 'faq:estacionamento'; })[0];
  if (!resp) throw new Error('sem a resposta da dúvida');
  T.ACOES.fechar(); T.ACOES.selecionar(botao('selecionar', { id: resp.id })); copiarCartao(); T.aprovarAtual();
  if (copiado().indexOf('Tem um estacionamento pago ao lado do prédio') < 0) throw new Error('a resposta não levou o texto aprovado');
  // Sem nome que exista no cadastro, nada é lido.
  T.ACOES['colar-novo'](botao('colar-novo'));
  var ctx = ctxCom({ '#colar-texto': campo('Oi'), '#colar-nome': campo('Pessoa Que Não Existe') });
  T.ACOES['colar-ler'](botao('colar-ler', {}, ctx));
  if (T.estado.colar.sugestoes) throw new Error('leu sem paciente');
});
run('etapa 4 · lista de espera: horário que vaga vai primeiro para quem está na lista daquela profissional', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  if (T.listaDeEspera('ana').map(function (p) { return p.nome; }).join() !== 'Bruno Zanetti,Eduardo Kühn') throw new Error('lista da Ana Carolina: ' + T.listaDeEspera('ana').map(function (p) { return p.nome; }));
  if (T.listaDeEspera('suellen').map(function (p) { return p.nome; }).join() !== 'Rui Santana') throw new Error('lista da Dra. Suellen');
  if (T.sugestoesDe(T.VAGA.v1)[0] !== 'juliana') throw new Error('os horários vagos das 06:00 não mudam');
  T.ACOES.ficha(botao('ficha', { nome: 'Eduardo Kühn' }));
  tem('Lista de espera · Ana Carolina', painel()); tem('data-acao="ficha-espera" data-prof="ana" data-valor="adiantar" aria-pressed="true"', painel()); tem('Pediu hoje, às 08:50', painel());
  T.ACOES.fechar();
  // Cancelou: o horário vira vago, e a lista de espera vem primeiro, na ordem em que cada um pediu.
  T.ACOES.confirmacoes(botao('confirmacoes'));
  T.ACOES['conf-cancelou'](botao('conf-cancelou', { id: '1-ana-1000-vinícius' }));
  var v = T.VAGAS[T.VAGAS.length - 1];
  if (!v.nova || v.data !== T.K[1] || v.hora !== '10:00') throw new Error('o horário não virou vago');
  var sug = T.sugestoesDe(v);
  if (sug[0] !== 'esp-SF-4510' || sug[1] !== 'esp-SF-3342' || sug.indexOf('bruno') >= 0) throw new Error('sugestões: ' + sug.join());
  if (T.PESSOAS['esp-SF-3342'].porque.indexOf('quer adiantar a sessão marcada para') < 0) throw new Error('o porquê do Eduardo');
  T.ACOES['abrir-vaga'](botao('abrir-vaga', { id: v.id }));
  tem('Horário que vagou hoje: primeiro, quem está na lista de espera com a Ana Carolina', painel()); tem('Lista de espera · aceita encaixe', painel()); tem('Quer trocar a de', painel());
  T.ACOES.convidar(botao('convidar', { vaga: v.id, pessoa: 'esp-SF-3342' }));
  T.ACOES['vaga-aceitou'](botao('vaga-aceitou', { id: v.id }));
  var p = T.pacientePorNome('Eduardo Kühn');
  if (p.espera.ana) throw new Error('o Eduardo continuou na lista de espera');
  if (T.estado.agenda.some(function (a) { return a.nome === 'Eduardo Kühn' && a.proc === 'Sessão 3 de 6'; })) throw new Error('o horário antigo não ficou livre');
  var m = T.estado.fila.filter(function (x) { return x.marcacao && x.nome === 'Eduardo Kühn'; })[0];
  if (!m || m.texto.indexOf('Ela fica no lugar da de') < 0) throw new Error('mensagem de marcação de quem adiantou: ' + (m && m.texto));
  // A Fer põe e tira da lista na ficha.
  T.ACOES.ficha(botao('ficha', { nome: 'Pedro Antunes' }));
  T.ACOES['ficha-espera'](botao('ficha-espera', { prof: 'ana', valor: 'encaixe' }));
  var pe = T.pacientePorNome('Pedro Antunes');
  if (!pe.espera.ana || pe.espera.ana.tipo !== 'encaixe') throw new Error('não entrou na lista');
  T.ACOES['ficha-espera'](botao('ficha-espera', { prof: 'ana', valor: 'encaixe' }));
  if (pe.espera.ana) throw new Error('não saiu da lista');
  T.ACOES.fechar();
});
run('etapa 4 · lista de espera da Dra. Suellen: a Paula cancela pela mensagem colada, e o Rui adianta a avaliação', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  T.ACOES.colar(botao('colar', { nome: 'Paula Rezende' }));
  T.ACOES['colar-ler'](botao('colar-ler', {}, ctxCom({ '#colar-texto': campo('Oi, vou precisar cancelar a avaliação de amanhã.'), '#colar-nome': campo('') })));
  tem('Cancelou a avaliação de amanhã, às 14h, com a Dra. Suellen', painel());
  T.ACOES['colar-registrar'](botao('colar-registrar'));
  if (T.estado.agenda.some(function (a) { return a.id === '1-suellen-1400-paula'; })) throw new Error('não cancelou');
  var v = T.VAGAS[T.VAGAS.length - 1];
  if (v.prof !== 'suellen' || T.sugestoesDe(v)[0] !== 'esp-SF-3131') throw new Error('o Rui devia vir primeiro: ' + T.sugestoesDe(v).join());
  T.ACOES.convidar(botao('convidar', { vaga: v.id, pessoa: 'esp-SF-3131' }));
  T.ACOES['vaga-aceitou'](botao('vaga-aceitou', { id: v.id }));
  var m = T.estado.fila.filter(function (x) { return x.marcacao && x.nome === 'Rui Santana'; })[0];
  if (!m || m.texto.indexOf('Como é a avaliação') < 0 || m.anexos.join() !== 'preparo') throw new Error('primeira avaliação amanhã: as boas-vindas vão junto');
  var nova = T.VAGAS[T.VAGAS.length - 1];
  if (nova.data !== T.K[2] || nova.hora !== '10:00' || nova.liberadoPor !== 'Rui Santana') throw new Error('o horário que o Rui deixou devia virar vago');
  T.ACOES.fechar();
});
run('etapa 4 · série do protocolo: a Dra. Suellen registrou "Fechou"; as 6 sessões pelos intervalos da clínica e a reavaliação no fim', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  var c = T.cartao('serie-beatriz-portela');
  if (!c || c.origem.indexOf('Fechou o protocolo') < 0 || c.origem.indexOf('09:18') < 0 || c.atrib !== 'agenda') throw new Error('cartão da série');
  var s = T.estadoSerie(c);
  var ana = s.plano.filter(function (x) { return x.prof === 'ana'; });
  var moni = s.plano.filter(function (x) { return x.prof === 'moni'; });
  var ult = s.plano[s.plano.length - 1];
  if (s.plano.length !== 8 || ana.length !== 6 || ana[0].hora !== '09:00' || ana[5].proc !== 'Sessão 6 de 6' || ult.proc !== 'Reavaliação' || ult.prof !== 'suellen') throw new Error('plano: ' + JSON.stringify(s.plano));
  // Luiz, 07/10/2026: quem fecha é encaminhado à Dra. Monielle, e a Fer marca a primeira consulta, no primeiro horário livre dela.
  if (moni.length !== 1 || moni[0].proc !== 'Consulta médica' || moni[0].fora) throw new Error('primeira consulta com a Dra. Monielle: ' + JSON.stringify(moni));
  var dm = (new Date(moni[0].data) - new Date(ana[0].data)) / 86400000;
  if (dm < 0 || dm > 6 || !T.gradeDoDia(moni[0].data, 'moni').linhas.some(function (l) { return l.hora === moni[0].hora && l.livres > 0; })) throw new Error('no primeiro horário livre dela: ' + dm);
  // Padrão do Luiz (07/10/2026): 20, 30, 40, 45 e 45 dias entre as seis sessões.
  var passos = [20, 30, 40, 45, 45];
  for (var i = 1; i < 6; i++) {
    var dif = (new Date(ana[i].data) - new Date(ana[i - 1].data)) / 86400000 - passos[i - 1];
    if (dif < 0 || dif > 6) throw new Error('intervalo da sessão ' + (i + 1) + ': ' + dif);
    if (!ana[i].fora && !T.gradeDoDia(ana[i].data, 'ana').linhas.some(function (l) { return l.hora === ana[i].hora && l.livres > 0; })) throw new Error('horário ocupado: ' + ana[i].data + ' ' + ana[i].hora);
  }
  var reav = (new Date(ult.data) - new Date(ana[5].data)) / 86400000 - 45;
  if (reav < 0 || reav > 6) throw new Error('a reavaliação vem uns 45 dias depois da última sessão: ' + reav);
  if (!s.plano.some(function (x) { return x.fora === 'agenda'; })) throw new Error('o que passa de 3 meses fica para quando a agenda abrir');
  location.hash = 'copiloto';
  T.ACOES.selecionar(botao('selecionar', { id: c.id }));
  ['Primeira sessão com a Ana Carolina', 'As 6 sessões com a Ana Carolina, a primeira consulta com a Dra. Monielle e a reavaliação com a Dra. Suellen', '<span class="serie-n">Dra. Monielle</span>', 'Primeira consulta, no primeiro horário livre dela', 'Reavaliação · Dra. Suellen', 'Intervalos da clínica', '✦', 'Mesmo horário', 'Da Dra. Suellen'].forEach(function (t) { tem(t); });
  // Outro começo: a série acompanha.
  var outra = s.opcoes[1];
  T.ACOES['serie-inicio'](botao('serie-inicio', { id: c.id, valor: outra.data + '|' + outra.hora }));
  if (T.estadoSerie(c).plano[0].data !== outra.data) throw new Error('a série não seguiu o começo escolhido');
  var nDentro = s.plano.filter(function (x) { return !x.fora && x.prof === 'ana'; }).length;
  tem('Marquei as ' + nDentro + ' no Clinicorp');
  // Nas Configurações, a regra da clínica.
  location.hash = 'configuracoes';
  tem('Da 1ª para a 2ª sessão'); tem('<dd>20 dias</dd>'); tem('Reavaliação com a Dra. Suellen');
  location.hash = 'copiloto';
  T.ACOES.selecionar(botao('selecionar', { id: c.id }));
  T.aprovarAtual();
  var sessoes = T.estado.agenda.filter(function (a) { return a.nome === 'Beatriz Portela' && a.prof === 'ana'; });
  if (sessoes.length < 2 || sessoes.length > 4 || c.status !== 'feita') throw new Error('sessões marcadas nos 3 meses da agenda: ' + sessoes.length);
  if (!T.estado.agenda.some(function (a) { return a.nome === 'Beatriz Portela' && a.prof === 'moni' && a.proc === 'Consulta médica'; })) throw new Error('a primeira consulta com a Dra. Monielle não foi marcada');
  var m = T.atual();
  if (!m || !m.marcacao || m.nome !== 'Beatriz Portela') throw new Error('a mensagem com as datas devia abrir em seguida');
  ['sessões com a Ana Carolina, biomédica da clínica, sugerimos estas datas, pelo intervalo do tratamento', '• Sessão 1:', 'Quais dão certo para você?', 'Cada sessão leva cerca de 1 hora', 'o horário de cada uma vai na confirmação, na véspera', 'a reavaliação com a Dra. Suellen fecha o ciclo', 'Rua Exemplo, 123', 'avise com pelo menos 24 horas'].forEach(function (t) { if (m.texto.indexOf(t) < 0) throw new Error('mensagem sem: ' + t); });
  // Luiz, 07/10/2026: a agenda guarda data e horário, e a mensagem leva só as datas.
  // Luiz, 08/10/2026: o sistema sugere as datas, e a Fer combina com o paciente.
  m.texto.split('\n').filter(function (l) { return l.indexOf('• Sessão') === 0; }).forEach(function (l) { if (/\d+h|às/.test(l)) throw new Error('a mensagem da série leva só as datas: ' + l); });
  if (!sessoes.every(function (a) { return /^\d\d:\d\d$/.test(a.hora); })) throw new Error('na agenda, cada sessão tem o horário');
  if (m.texto.indexOf('a primeira é amanhã') >= 0) throw new Error('a primeira não é amanhã');
  if (m.texto.indexOf('Para a primeira consulta com a Dra. Monielle, a médica da clínica, sugerimos') < 0) throw new Error('a mensagem leva a primeira consulta com a Dra. Monielle');
  if (m.texto.indexOf('• Sessão 6:') >= 0) throw new Error('a 6ª passa de 3 meses: fica para quando a agenda abrir');
  // Na saída, as sessões já marcadas: é só conferir.
  T.ACOES['rec-saiu'](botao('rec-saiu', { id: '0-suellen-0900-beatriz' })); tem('Já marcadas: a primeira é', painel());
  T.ACOES['rec-registrar-saida'](botao('rec-registrar-saida', { id: '0-suellen-0900-beatriz' }, ctxCom({ '.painel-corpo': new El('pc'), '.opcao[data-grupo="fechou"][aria-pressed="true"]': opc('sim', 'Fechou'), '.opcao[data-grupo="forma"][aria-pressed="true"]': opc('pix', 'PIX') })));
  if (itemDe('0-suellen-0900-beatriz').status !== 'saiu') throw new Error('saída não registrada');
  // Fechou: a Fer lança o pacote que a Dra. Suellen combinou (Luiz, 07/10/2026).
  if (!T.pacientePorNome('Beatriz Portela').pag.some(function (x) { return x.item === 'protocolo' && x.dia === 0 && x.forma === 'PIX'; })) throw new Error('o pacote não foi lançado');
  T.ACOES.fechar();
});
run('Luiz, 07/10 · série: com a agenda da Dra. Monielle cheia, a primeira consulta fica na lista da Fer, sem horário inventado', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  var c = T.estado.fila.filter(function (x) { return x.id === 'serie-beatriz-portela'; })[0];
  if (!c) throw new Error('sem a série da Beatriz');
  var plano = T.estadoSerie(c).plano;
  var m = plano.filter(function (x) { return x.prof === 'moni'; })[0];
  if (!m || m.fora) throw new Error('a série acha o primeiro horário livre dela');
  // Enche a agenda da Dra. Monielle nas duas semanas da primeira sessão.
  var ini = plano.filter(function (x) { return x.prof === 'ana'; })[0].data.split('-');
  var d0 = new Date(+ini[0], +ini[1] - 1, +ini[2]);
  var ch = function (d) { return d.getFullYear() + '-' + ('0' + (d.getMonth() + 1)).slice(-2) + '-' + ('0' + d.getDate()).slice(-2); };
  for (var i = 0; i < 14; i += 1) {
    var k = ch(new Date(d0.getFullYear(), d0.getMonth(), d0.getDate() + i));
    T.gradeDoDia(k, 'moni').linhas.forEach(function (l, n) { if (l.livres > 0) T.estado.agenda.push({ id: 'cheia-' + k + '-' + n, data: k, prof: 'moni', hora: l.hora, nome: 'Paciente de teste', proc: 'Consulta médica', status: 'marcado', faltam: [] }); });
  }
  var m2 = T.estadoSerie(c).plano.filter(function (x) { return x.prof === 'moni'; })[0];
  if (m2.fora !== 'cheia') throw new Error('a semana dela está cheia: ' + m2.fora);
  location.hash = 'copiloto';
  T.ACOES.selecionar(botao('selecionar', { id: c.id }));
  tem('Primeira consulta: a agenda dela está cheia, fica na sua lista'); tem('A partir de ');
  T.ACOES['serie-marcar'](botao('serie-marcar', { id: c.id }));
  tem('a primeira consulta com a Dra. Monielle fica na sua lista, para o primeiro horário livre dela', __els.toast.innerHTML);
  if (T.estado.agenda.some(function (a) { return a.nome === 'Beatriz Portela' && a.prof === 'moni'; })) throw new Error('não marca a consulta sem horário livre');
  var msg = T.estado.fila.filter(function (x) { return x.nome === 'Beatriz Portela' && x.status === 'pendente' && x.texto && x.texto.indexOf('assim que abrir um horário dela') >= 0; });
  if (msg.length !== 1) throw new Error('a mensagem avisa que a consulta é marcada quando abrir um horário: ' + msg.length);
  T.ACOES.reiniciar(botao('reiniciar'));
});
run('etapa 4 · série do protocolo: quem fecha na saída marca a primeira, e as outras 5 vão pela série', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  T.ACOES.recepcao(botao('recepcao')); tem('Fechou: marcar as 6 sessões na saída', painel());
  T.ACOES['rec-saiu'](botao('rec-saiu', { id: '0-suellen-0900-beatriz' })); tem('A Dra. Suellen já registrou: fechou o protocolo.', painel()); tem('aria-pressed="true">Fechou</button>', painel());
  T.ACOES['rec-chegou'](botao('rec-chegou', { id: '0-suellen-1400-letícia' }));
  T.ACOES['rec-registrar-chegada'](botao('rec-registrar-chegada', { id: '0-suellen-1400-letícia', pedidos: 'whats,origem' }));
  T.ACOES['rec-saiu'](botao('rec-saiu', { id: '0-suellen-1400-letícia' })); tem('as outras 5 vão de uma vez, pela série', painel());
  var s = T.livresAPartir('ana', daquiA(7), 1)[0];
  T.ACOES['rec-registrar-saida'](botao('rec-registrar-saida', { id: '0-suellen-1400-letícia' }, ctxCom({ '.painel-corpo': new El('pc'), '.opcao[data-grupo="fechou"][aria-pressed="true"]': opc('sim', 'Fechou'), '.opcao[data-grupo="proxima"][aria-pressed="true"]': opc(s.data + '|' + s.hora, 'x'), '.opcao[data-grupo="forma"][aria-pressed="true"]': opc('credito', 'Cartão de crédito'), '.opcao[data-grupo="parcelas"][aria-pressed="true"]': opc('4', '4x') })));
  var c = T.cartao('serie-leticia-barros');
  if (!c || c.status !== 'pendente' || c.de !== 'Da recepção') throw new Error('a série da Letícia não entrou');
  var st = T.estadoSerie(c);
  var stAna = st.plano.filter(function (x) { return x.prof === 'ana'; });
  if (st.primeiroN !== 2 || stAna.length !== 5 || st.plano[st.plano.length - 1].proc !== 'Reavaliação' || st.ja[0].data !== s.data || !st.plano.some(function (x) { return x.prof === 'moni'; })) throw new Error('as outras 5, a primeira consulta com a Dra. Monielle e a reavaliação: ' + JSON.stringify(st.plano));
  var d2 = (new Date(stAna[0].data) - new Date(s.data)) / 86400000 - 20;
  if (d2 < 0 || d2 > 6) throw new Error('a 2ª sessão vem uns 20 dias depois da 1ª: ' + d2);
  if (T.grupoDe(T.pacientePorNome('Letícia Barros')) !== 'tratamento') throw new Error('fechou: em tratamento');
  if (!T.pacientePorNome('Letícia Barros').pag.some(function (x) { return x.item === 'protocolo' && x.forma === 'cartão de crédito em 4x'; })) throw new Error('o pacote no cartão em 4x');
  T.ACOES.fechar();
});
run('Luiz, 07/10 · home care: cada kit dura o tempo definido; a Fer separa para a Ana Carolina entregar na última vinda antes de acabar e dá baixa na saída; sem vinda marcada, combina a retirada', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  if (T.HOMECARE.nutri.dura !== 120 || T.HOMECARE.fort.dura !== 120 || T.HOMECARE.tonic.dura !== 75) throw new Error('durações definitivas');
  var v = T.vencimentosHomecare(T.estado.pacientes, T.estado.agenda).map(function (x) { return x.p.nome + ':' + x.prod + ':' + x.tipo + ':' + x.restam; }).sort().join();
  if (v !== 'Aline Castilho:fort:retirada:8,Eduardo Kühn:nutri:retirada:5,Fabrício Ventura:tonic:retirada:-18,Gabriela Siqueira:tonic:separar:40,Larissa Menezes:tonic:separar:35,Rubens Teodoro:tonic:retirada:3') throw new Error('regra: ' + v);
  // A Gabriela recebe na sessão de hoje e a Larissa na de amanhã: o Capilare Tonic acaba antes da vinda seguinte.
  var g = cartaoDe('hc-SF-1034-separar');
  if (!g || g.tipo !== 'separar' || g.prods.join() !== 'tonic' || g.pergunta.indexOf('Separe o Capilare Tonic dela e passe para a Ana Carolina antes da sessão de hoje, às 16h') < 0) throw new Error('separar da Gabriela: ' + (g && g.pergunta));
  var l = cartaoDe('hc-SF-1846-separar');
  if (!l || l.pergunta.indexOf('antes da sessão de amanhã, às 9h') < 0) throw new Error('separar da Larissa: ' + (l && l.pergunta));
  if (g.texto) throw new Error('separar é tarefa da Fer, sem mensagem ao paciente');
  // O Caio tem sessão hoje, mas o kit dele dura até depois da vinda seguinte: nada a separar.
  if (cartaoDe('hc-SF-0623-separar')) throw new Error('o Capilare Tonic do Caio dura até depois da próxima sessão');
  location.hash = 'copiloto';
  T.ACOES.selecionar(botao('selecionar', { id: g.id }));
  tem('Separei e passei para a Ana Carolina'); tem('Quem entrega ao paciente e explica o uso é a Ana Carolina'); tem('Para a Ana Carolina');
  T.aprovarAtual();
  if (g.status !== 'feita') throw new Error('não registrou que separou');
  // Na saída, o que a Fer separou já vem marcado; registrar a saída dá baixa, e a próxima entrega conta de hoje.
  var id = '0-ana-1600-gabriela';
  T.ACOES['rec-chegou'](botao('rec-chegou', { id: id }));
  T.ACOES['rec-registrar-chegada'](botao('rec-registrar-chegada', { id: id, pedidos: '' }));
  T.ACOES['rec-saiu'](botao('rec-saiu', { id: id }));
  tem('Você separou Capilare Tonic para ela entregar hoje: confira e dê baixa.', painel()); tem('data-valor="tonic" aria-pressed="true"', painel()); tem('data-valor="nutri" aria-pressed="false"', painel());
  var s = T.livresAPartir('ana', daquiA(45), 1)[0];
  T.ACOES['rec-registrar-saida'](botao('rec-registrar-saida', { id: id }, ctxCom({ '.painel-corpo': new El('pc'), '.opcao[data-grupo="proxima"][aria-pressed="true"]': opc(s.data + '|' + s.hora, 'x'), 'TODOS .opcao[data-grupo="hc"][aria-pressed="true"]': [opc('tonic', 'Capilare Tonic')] })));
  tem('baixa do home care que a Ana Carolina entregou', __els.toast.innerHTML);
  T.ACOES.fechar();
  var tonic = T.pacientePorNome('Gabriela Siqueira').hc.filter(function (h) { return h.prod === 'tonic'; })[0];
  if (tonic.dia !== 0) throw new Error('baixa: a próxima entrega conta de hoje');
  // Sem vinda marcada antes de acabar, a Fer combina a retirada; o remédio só fala da entrega.
  var e = cartaoDe('hc-SF-3342-retirada');
  if (!e || e.texto.indexOf('Quando você pode passar na clínica para retirar o novo, ou prefere receber na sua sessão') < 0 || e.texto.indexOf('A Ana Carolina entrega para você.') < 0) throw new Error('retirada do Eduardo: ' + (e && e.texto));
  var f = cartaoDe('hc-SF-4187-retirada');
  if (!f || /\bdose\b|\btome\b|\baplique\b|comprimid|vezes ao dia|\buso\b/i.test(f.texto) || f.nota.indexOf('fala só da entrega') < 0) throw new Error('Capilare Fort: só a entrega');
  if (cartaoDe('hc-SF-0479-retirada')) throw new Error('o que já acabou fica com o agente de retenção');
  // Sem o pacote de 90 dias: cada kit pela duração dele.
  T.ACOES.ficha(botao('ficha', { nome: 'Clara Medeiros' })); naoTem('Pacote de home care', painel()); tem('Entregue pela Ana Carolina', painel()); tem('dura 120 dias', painel()); tem('dura 75 dias', painel()); T.ACOES.fechar();
  location.hash = 'atribuicoes'; tem('Capilare Tonic, 75'); tem('Separar o home care'); naoTem('segunda entrega do pacote');
  location.hash = 'configuracoes'; tem('Entrega do home care'); naoTem('Pacote de home care'); naoTem('Capilare Tonic 45');
  location.hash = 'copiloto';
});
run('Luiz, 07/10 · caminho do exame: chegou, a Fer registra e passa direto para a Dra. Monielle, que cuida de tudo; acaba ali', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  if (cartaoDe('exame-natalia')) throw new Error('não há o que esperar da Dra. Monielle: ela não registra nada no sistema');
  T.ACOES.ficha(botao('ficha', { nome: 'Natália Freitas' })); tem('Passado à Dra. Monielle', painel()); tem('ela cuida de tudo direto com ela', painel()); naoTem('Só na demonstração', painel()); naoTem('ainda não viu', painel()); T.ACOES.fechar();
  // Vanessa: o lembrete da entrega; quando chega, a Fer registra e passa direto para a Dra. Monielle.
  var v = cartaoDe('vanessa');
  if (v.tipo !== 'exame' || v.etapa !== 'entrega' || v.texto.indexOf('a Dra. Monielle pediu o seu exame de sangue') < 0) throw new Error('exame da Vanessa: ' + v.texto);
  location.hash = 'copiloto';
  T.ACOES.selecionar(botao('selecionar', { id: v.id }));
  ['Caminho do exame', 'Pedido em', 'Passar à Dra. Monielle', 'Já chegou: registrar a entrega'].forEach(function (t) { tem(t); });
  T.ACOES['exame-entregue'](botao('exame-entregue', { cod: 'SF-4293' }));
  var pv = T.pacientePorNome('Vanessa Kowalski');
  if (v.status !== 'feita' || pv.ex.entregue !== 0 || !pv.ex.avisada) throw new Error('entrega não registrada: ' + v.status);
  if (!T.estado.log.some(function (l) { return /passou direto para a Dra\. Monielle/.test(l[1]); })) throw new Error('sem a passagem para a Dra. Monielle');
  if (pv.enc != null) throw new Error('o exame não vira consulta pelo sistema');
  T.ACOES.recepcao(botao('recepcao')); naoTem('Exame: com a Dra. Monielle', painel()); T.ACOES.fechar();
});
run('etapa 4 · ligação que marca: a mensagem de marcação vem em seguida', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  location.hash = 'copiloto';
  T.ACOES.selecionar(botao('selecionar', { id: 'juliana' }));
  var c = T.atual();
  var alt = c.opcoesRemarcar[1];
  T.ACOES.desfecho(botao('desfecho', { r: 'remarcou' }, ctxCom({ '.sub': new El('sub') })));
  T.ACOES['concluir-desfecho'](botao('concluir-desfecho', { r: 'remarcou' }, ctxCom({ '.opcao[data-grupo="slot"][aria-pressed="true"]': opc(alt[0], alt[1]) })));
  var m = T.atual();
  if (!m || !m.marcacao || m.nome !== 'Juliana Prado' || m.texto.indexOf('Sua sessão com a Ana Carolina, biomédica da clínica, ficou marcada para') < 0) throw new Error('mensagem depois da ligação: ' + (m && m.texto));
});
run('etapa 4 · Configurações, Atribuições e telas: as regras da experiência do paciente', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  location.hash = 'configuracoes';
  ['Mensagens para pacientes', 'Rua Exemplo, 123', 'Endereço da clínica (fictício)', 'Série do protocolo e lista de espera', 'Intervalo entre as sessões', 'Mensagem de marcação', 'Boas-vindas da primeira avaliação', 'Mensagem colada', 'Caminho do exame', 'pelos intervalos da clínica', 'Exemplo de mensagem de marcação', 'Na véspera, mando as orientações para a avaliação'].forEach(function (t) { tem(t); });
  naoTem('a decidir');
  location.hash = 'atribuicoes'; ['boas-vindas junto', 'cole a mensagem', 'Ao marcar, mandar a mensagem de marcação', 'exames (do pedido ao retorno, sem interpretar)'].forEach(function (t) { tem(t); });
  location.hash = 'pacientes'; tem('Colar mensagem de paciente');
  T.ACOES.ficha(botao('ficha', { nome: 'Ricardo Sato' })); tem('data-acao="colar" data-nome="Ricardo Sato"', painel()); T.ACOES.fechar();
  T.ACOES['ver-log'](botao('ver-log')); tem('Fechou o protocolo” na avaliação de Beatriz Portela', painel()); tem('2 entregas para separar para a Ana Carolina', painel()); T.ACOES.fechar();
  location.hash = 'copiloto';
});
run('conferência única: a peça inteira é conferida, cartão por cartão, e o selo diz a verdade', function () {
  var a = T.conferirPeca({ titulo: 'Como é a avaliação', legenda: 'Passo a passo da avaliação.', slides: [{ frase: 'A conversa' }, { titulo: 'Resultado garantido', texto: 'em 3 meses' }] });
  if (!a.length || a[0].indexOf('promessa') < 0 || a[0].indexOf('no cartão 2') < 0) throw new Error(JSON.stringify(a));
  if (T.conferirPeca({ titulo: 'Bastidores', legenda: 'Paciente Jorge Almeida, 50 anos, viu a diferença.' }).join(' ').indexOf('identificar um paciente') < 0) throw new Error('paciente identificável');
  if (T.conferirPeca({ titulo: 'Bastidores', legenda: 'A sala de fototerapia, com luz controlada.' }).length) throw new Error('barrou legenda limpa');
});
run('conferência única: mensagem editada com promessa não sai; sem promessa, sai', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  var c = cartaoDe('fabricio');
  T.ACOES.selecionar(botao('selecionar', { id: 'fabricio' }));
  T.editarAtual();
  getEl('editor-fabricio').value = c.texto + '\n\nCom o protocolo, o cabelo volta a crescer!';
  T.ACOES['salvar-edicao'](botao('salvar-edicao'));
  if (c.status !== 'pendente') throw new Error('saiu mensagem com promessa');
  getEl('editor-fabricio').value = c.texto;
  T.ACOES['salvar-edicao'](botao('salvar-edicao'));
  if (c.status === 'pendente') throw new Error('a mensagem sem promessa não saiu');
});
run('conferência única: o guardião e a lista de remédio pegam as frases comuns', function () {
  var l = T.lerMensagem('Ficou com inchaço na nuca depois da sessão', 'Caio Ferraz');
  var r = l.filter(function (x) { return x.tipo === 'relato'; })[0];
  if (!r || r.palavra !== 'inchaço') throw new Error(JSON.stringify(l));
  if (T.lerMensagem('Ela usa finasterida, tem problema?')[0].tipo !== 'remedio') throw new Error('finasterida no colar');
  var c = T.responder('A Aline perguntou se pode usar o Capilare Fort com a loção');
  if (c.tipo !== 'clinico' || !c.dados.remedio) throw new Error('Capilare Fort na conversa');
});
run('convite ao paciente não cita o nome do remédio (regra única proposta no caso Aline × Paulo)', function () {
  var citam = [];
  Object.keys(T.PESSOAS).forEach(function (k) {
    var p = T.PESSOAS[k];
    if (!p.gancho) return;
    var msg = T.convite(k, T.VAGA.v6);
    if (T.CONFERE.remedios(msg).length || T.CONFERE.tem(T.CONFERE.NOME_REMEDIO, msg)) citam.push(k);
  });
  if (citam.length) throw new Error('convite cita remédio: ' + citam.join(', '));
  if (T.convite('otavior', T.VAGA.v6).indexOf('primeira consulta com a Dra. Monielle') < 0) throw new Error('convite do Otávio');
});
run('noite 2: pedir uma por uma não marca nada sozinho, e a Fer para quando quiser', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  location.hash = 'copiloto'; T.ACOES.filtrar(botao('filtrar', { atrib: 'confirmacoes' }));
  var enviadas = function () { return T.estado.agenda.filter(function (a) { return a.conf === 'enviada'; }).length; };
  var antes = enviadas();
  T.ACOES['conf-todas'](botao('conf-todas'));
  if (enviadas() !== antes) throw new Error('o atalho marcou pedido como enviado sem a Fer');
  var primeira = T.proximasDaSequencia()[0];
  tem('Pedido 1 de'); tem(primeira.nome);
  T.ACOES['conf-seq-copiar'](botao('conf-seq-copiar', { id: primeira.id }));
  if (itemDe(primeira.id).conf !== 'a-enviar') throw new Error('copiar não é enviar');
  T.ACOES['conf-seq-enviei'](botao('conf-seq-enviei', { id: primeira.id }));
  if (itemDe(primeira.id).conf !== 'enviada' || enviadas() !== antes + 1) throw new Error('Enviei marca só aquele paciente');
  var segunda = T.proximasDaSequencia()[0];
  T.ACOES['conf-seq-pular'](botao('conf-seq-pular', { id: segunda.id }));
  if (itemDe(segunda.id).conf !== 'a-enviar') throw new Error('pular não envia');
  tem('Pedido 3 de');
  T.ACOES['conf-seq-parar'](botao('conf-seq-parar'));
  if (T.estado.confSeq) throw new Error('não parou');
  if (enviadas() !== antes + 1) throw new Error('parar não envia o resto');
  tem('Pedir uma por uma');
  if (!T.estado.log.some(function (l) { return l[1].indexOf('Você pediu a confirmação de ' + primeira.nome) === 0; })) throw new Error('uma linha por paciente no registro');
  T.ACOES.reiniciar(botao('reiniciar'));
});
run('noite 2: a trilha do dia diz o que o copiloto preparou', function () {
  location.hash = 'feito';
  tem('O que o copiloto preparou'); naoTem('fez sozinho');
  location.hash = 'copiloto';
});
run('Luiz, 07/10 · sem selo de pausa no sistema: a equipe combina direto com a Dra. Monielle', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  var s = T.livresAPartir('ana', daquiA(10), 1)[0];
  var quem = new El('quem'); quem.value = 'Teste Pausado';
  T.ACOES['confirmar-marcacao'](botao('confirmar-marcacao', { data: s.data, hora: s.hora, prof: 'ana' }, ctxCom({ '.painel-corpo': new El('pc'), '#f-quem': quem, '.opcao[data-grupo="tipo"][aria-pressed="true"]': opc('Sessão', 'Sessão') })));
  if (!T.estado.agenda.some(function (a) { return a.nome === 'Teste Pausado' && a.prof === 'ana'; })) throw new Error('nada trava a marcação: a pausa não fica no sistema');
  T.ACOES.fechar();
  T.ACOES['ir-data'](botao('ir-data', { data: T.K[1] })); naoTem('Em pausa');
  T.ACOES.reiniciar(botao('reiniciar'));
});
run('Luiz, 07/10: o horário vago da Dra. Suellen é para reavaliação, quem quer adiantar, avaliação nova e o encaixe de quem avaliou e não fechou', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  T.ACOES['abrir-vaga'](botao('abrir-vaga', { id: 'v4' }));
  var h = painel();
  tem('Avaliação ou reavaliação com a Dra. Suellen', h);
  tem('a reavaliação de quem terminou as sessões com a Ana Carolina: é a última sessão do ciclo, e a Dra. Suellen dá a alta', h);
  tem('quer adiantar', h); tem('Avaliações novas', h); tem('o encaixe de quem avaliou e não fechou', h);
  tem('Renata Rocha', h); tem('Diego Andrade', h);
  if (h.indexOf('Renata Rocha') > h.indexOf('Diego Andrade')) throw new Error('a reavaliação de fim de ciclo vem antes do encaixe');
  T.ACOES.fechar();
});
run('Luiz, 07/10 · contato novo: a primeira resposta pede os dados; a Fer passa direto, recebe o pedido da Dra. Monielle e manda', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  var c = cartaoDe('marcelo');
  if (c.texto.indexOf('me mande, por favor: nome completo, CPF, data de nascimento, endereço e e-mail') < 0 || c.texto.indexOf('exame de sangue dos últimos 90 dias') < 0) throw new Error('a primeira resposta pede os dados: ' + c.texto);
  location.hash = 'copiloto';
  T.ACOES.selecionar(botao('selecionar', { id: 'marcelo' }));
  T.aprovarAtual();
  var pe = cartaoDe('pedido-exame-marcelo-arruda');
  if (!pe || pe.etapa !== 'dados' || pe.status !== 'pendente') throw new Error('sem o cartão do pedido');
  T.ACOES.selecionar(botao('selecionar', { id: pe.id }));
  tem('Registrar e passar para a Dra. Monielle'); tem('Aqui fica só o final do CPF, que não vai para a IA.');
  var ctx = function (cpf) { return ctxCom({ '#pe-nome': campo('Marcelo Arruda Lima'), '#pe-cpf': campo(cpf), '#pe-nasc': campo('1990-05-12'), '#pe-end': campo('Rua Exemplo, 456, Batel, Curitiba'), '#pe-email': campo('marcelo@exemplo.com'), '.painel-corpo': new El('pc') }); };
  T.ACOES['pedido-registrar'](botao('pedido-registrar', { id: pe.id }, ctx('123')));
  if (pe.etapa !== 'dados') throw new Error('CPF incompleto não registra');
  T.ACOES['pedido-registrar'](botao('pedido-registrar', { id: pe.id }, ctx('000.111.222-33')));
  if (pe.etapa !== 'monielle' || pe.dados.cpfFim !== '33' || JSON.stringify(pe).indexOf('00011122233') >= 0) throw new Error('o CPF completo não fica no copiloto');
  tem('Com a Dra. Monielle: ela faz o pedido do exame de sangue e manda direto para você'); tem('CPF terminado em 33'); tem('Recebi o pedido da Dra. Monielle'); naoTem('Só na demonstração');
  T.ACOES['pedido-recebido'](botao('pedido-recebido', { id: pe.id }));
  if (pe.tipo !== 'mensagem' || pe.texto.indexOf('Segue o pedido do seu exame de sangue, feito pela Dra. Monielle') < 0 || /normal|alterad|remédio/i.test(pe.texto)) throw new Error('mensagem do pedido: ' + pe.texto);
  T.aprovarAtual();
  var p = T.pacientePorNome('Marcelo Arruda');
  if (pe.status !== 'feita' || !p.ex || p.ex.pedido !== 0) throw new Error('pedido mandado e registrado');
});
run('Luiz, 07/10 · saída: o retorno que a Ana Carolina passou vem marcado, com os horários na tela; sem retorno passado, o padrão do ciclo', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  var id = '0-ana-1000-caio';
  T.ACOES['rec-chegou'](botao('rec-chegou', { id: id }));
  T.ACOES['rec-registrar-chegada'](botao('rec-registrar-chegada', { id: id, pedidos: '' }));
  T.ACOES['rec-saiu'](botao('rec-saiu', { id: id }));
  var h = painel();
  tem('A Ana Carolina passou o retorno em 35 dias, fora do padrão da clínica', h); tem('Em 35 dias · indicado pela Ana Carolina', h); tem('opcao--sugerida', h);
  tem('data-dias="35" aria-pressed="true"', h); tem('data-grupo="proxima" data-valor="', h); tem('Não quis marcar agora', h);
  T.ACOES.fechar();
  var g = '0-ana-1600-gabriela';
  T.ACOES['rec-chegou'](botao('rec-chegou', { id: g }));
  T.ACOES['rec-registrar-chegada'](botao('rec-registrar-chegada', { id: g, pedidos: '' }));
  T.ACOES['rec-saiu'](botao('rec-saiu', { id: g }));
  tem('O padrão do ciclo vem marcado: 45 dias', painel()); tem('Em 45 dias · padrão do ciclo', painel()); tem('data-dias="45" aria-pressed="true"', painel());
  T.ACOES.fechar();
});
run('Luiz, 07/10 · lançamento: fechou na avaliação, a Fer lança o pacote, o valor e a forma de pagamento; o home care vai sem valor', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  var id = '0-suellen-1400-letícia';
  T.ACOES['rec-chegou'](botao('rec-chegou', { id: id }));
  T.ACOES['rec-registrar-chegada'](botao('rec-registrar-chegada', { id: id, pedidos: '' }));
  T.ACOES['rec-saiu'](botao('rec-saiu', { id: id }));
  var h = painel();
  ['Se fechou: o lançamento do pacote', 'Protocolo completo (6 sessões)', 'R$ 2.600', 'o home care vem incluído, sem valor à parte', 'PIX', 'Cartão de crédito', 'Cartão de débito', 'Boleto', 'Parcelas, no cartão de crédito', '4x', 'A análise do financeiro fica com ela, na Gestão', 'Até o corte, lance também no Clinicorp'].forEach(function (t) { tem(t, h); });
  var s = T.livresAPartir('ana', daquiA(7), 1)[0];
  var base = { '.painel-corpo': new El('pc'), '.opcao[data-grupo="fechou"][aria-pressed="true"]': opc('sim', 'Fechou'), '.opcao[data-grupo="proxima"][aria-pressed="true"]': opc(s.data + '|' + s.hora, 'x') };
  var p = T.pacientePorNome('Letícia Barros');
  var n0 = p.pag.length;
  T.ACOES['rec-registrar-saida'](botao('rec-registrar-saida', { id: id }, ctxCom(base)));
  if (itemDe(id).status === 'saiu' || p.pag.length !== n0) throw new Error('fechou sem lançar a forma de pagamento');
  var cred = Object.assign({}, base); cred['.opcao[data-grupo="forma"][aria-pressed="true"]'] = opc('credito', 'Cartão de crédito');
  T.ACOES['rec-registrar-saida'](botao('rec-registrar-saida', { id: id }, ctxCom(cred)));
  if (itemDe(id).status === 'saiu') throw new Error('no cartão de crédito, pede as parcelas');
  cred['.opcao[data-grupo="parcelas"][aria-pressed="true"]'] = opc('4', '4x');
  T.ACOES['rec-registrar-saida'](botao('rec-registrar-saida', { id: id }, ctxCom(cred)));
  var novo = p.pag[p.pag.length - 1];
  if (itemDe(id).status !== 'saiu' || p.pag.length !== n0 + 1 || novo.item !== 'protocolo' || novo.dia !== 0 || novo.forma !== 'cartão de crédito em 4x') throw new Error('lançamento: ' + JSON.stringify(novo));
  tem('pacote lançado: R$ 2.600, cartão de crédito em 4x', __els.toast.innerHTML);
  if (!T.estado.auditoria.some(function (x) { return x.cod === p.cod && /lançou o pacote combinado na avaliação: protocolo completo \(6 sessões\), R\$ 2\.600, cartão de crédito em 4x/.test(x.texto); })) throw new Error('o lançamento fica no registro da ficha');
  T.ACOES.fechar();
  T.ACOES.ficha(botao('ficha', { nome: 'Letícia Barros' })); tem('Protocolo completo (6 sessões)', painel()); tem('cartão de crédito em 4x', painel()); T.ACOES.fechar();
  // No cartão em 1x, só "cartão de crédito"; depois do corte, sem o lembrete do Clinicorp.
  if (T.textoForma('credito', '1') !== 'cartão de crédito' || T.textoForma('pix') !== 'PIX' || T.textoForma('credito', '3') !== 'cartão de crédito em 3x') throw new Error('forma de pagamento');
});
run('Luiz, 07/10 · ficha: a Fer vê tudo e lança um recebimento, como uma parcela ou um acerto que a Ana Carolina recebeu', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  T.ACOES.ficha(botao('ficha', { nome: 'Ricardo Sato' }));
  tem('Lançar um recebimento', painel()); naoTem('Só para consulta', painel());
  T.ACOES['ficha-receber'](botao('ficha-receber'));
  var h = painel();
  ['O que foi recebido', 'Parcela do pacote', 'Sessão de manutenção', 'Acerto', 'Valor, em reais', 'Forma de pagamento', 'Dinheiro', 'Quem recebeu', 'Você, na recepção', 'A Ana Carolina, na sessão', 'data-acao="ficha-lancar"'].forEach(function (t) { tem(t, h); });
  var p = T.pacientePorNome('Ricardo Sato');
  var n0 = p.pag.length;
  var ctx = function (valor) { var v = new El('v'); v.value = valor; return ctxCom({ '.opcao[data-grupo="rec-item"][aria-pressed="true"]': opc('acerto', 'Acerto'), '.opcao[data-grupo="rec-forma"][aria-pressed="true"]': opc('dinheiro', 'Dinheiro'), '.opcao[data-grupo="rec-quem"][aria-pressed="true"]': opc('ana', 'A Ana Carolina, na sessão'), 'input[data-rec-valor]': v }); };
  T.ACOES['ficha-lancar'](botao('ficha-lancar', {}, ctx('')));
  if (p.pag.length !== n0) throw new Error('lançou sem valor');
  tem('Preencha o que foi recebido, o valor, a forma e quem recebeu.', __els.toast.innerHTML);
  T.ACOES['ficha-lancar'](botao('ficha-lancar', {}, ctx('120')));
  var x = p.pag[p.pag.length - 1];
  if (p.pag.length !== n0 + 1 || x.item !== 'acerto' || x.valor !== 120 || x.quem !== 'ana' || x.forma !== 'dinheiro' || x.dia !== 0) throw new Error('recebimento: ' + JSON.stringify(x));
  h = painel();
  tem('R$ 120', h); tem('a Ana Carolina recebeu', h); tem('3.070</strong>', h); tem('Lançar um recebimento', h);
  if (!T.estado.auditoria.some(function (r) { return r.cod === p.cod && r.texto === 'lançou um recebimento: acerto, R$ 120, dinheiro, que a Ana Carolina recebeu'; })) throw new Error('registro da ficha');
  tem('Recebimento lançado. A Dra. Suellen vê na Gestão.', __els.toast.innerHTML);
  T.ACOES.fechar();
});
run('Luiz, 07/10 · insumos: a gestão é da Fer; quanto dura cada item pela agenda, o que está acabando e o aviso à Dra. Suellen', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  // Os mesmos dados do copiloto da Ana Carolina: id, nome, curto, unidade, quantidade e mínimo.
  if (T.INSUMOS_BASE.length !== 9 || T.INSUMOS_BASE.some(function (x) { return !x.id || !x.nome || !x.curto || x.curto.length !== 2 || !x.un || x.un.length !== 2 || typeof x.qtd !== 'number' || typeof x.minimo !== 'number'; })) throw new Error('estrutura dos insumos');
  if (T.estado.insumos[0] === T.INSUMOS_BASE[0]) throw new Error('o estoque do dia é uma cópia: a base não muda');
  // O kit padrão: toda sessão leva luvas, touca e lençol; MMP e intradermoterapia somam os seus.
  var k = T.kitDe(['foto', 'mmp']);
  if (k.luvas !== 2 || k.touca !== 1 || k.lencol !== 1 || k.cartucho !== 1 || k['ampola-mmp'] !== 1 || k.gaze !== 2 || k.seringa) throw new Error('kit: ' + JSON.stringify(k));
  if (Object.keys(T.kitDe([])).length) throw new Error('sem procedimento, sem kit');
  // Os procedimentos do pacote em cada sessão: fototerapia sempre; MMP e intradermoterapia se alternam.
  var pr = function (x) { return T.procsDe({ proc: x }).join(); };
  if (pr('Sessão 1 de 6') !== 'foto,intra' || pr('Sessão 2 de 6') !== 'foto,mmp' || pr('Manutenção') !== 'foto,mmp' || pr('Avaliação') !== '') throw new Error('procedimentos');
  var acab = T.estado.insumos.filter(T.acabando).map(function (x) { return x.id; }).sort().join();
  if (acab !== 'ampola-mmp,cartucho,touca') throw new Error('acabando: ' + acab);
  var cart = T.estado.insumos.filter(function (x) { return x.id === 'cartucho'; })[0];
  if (T.diasDe(cart) !== 1 || T.usoPorDia('cartucho') !== 4) throw new Error('duração pela agenda: ' + T.diasDe(cart));
  location.hash = 'copiloto'; tem('data-acao="ir-insumos"'); tem('title="3 insumos acabando"'); // desde 09/10/2026, os insumos são uma linha da lista do Copiloto
  T.ACOES['ir-insumos'](botao('ir-insumos'));
  if (location.hash !== '#insumos') throw new Error('não abriu os insumos');
  ['Insumos da sala da Ana Carolina', 'Acabando, no ritmo da agenda de hoje, amanhã e depois', 'Avisar a Dra. Suellen', 'Kit padrão de cada sessão', 'Em dia', 'Dá para 1 dia.', 'Quem compra os insumos ainda está em aberto'].forEach(function (t) { tem(t); });
  T.ACOES['avisar-insumo'](botao('avisar-insumo', { id: 'cartucho' }));
  if (!T.estado.avisosInsumo.cartucho) throw new Error('aviso não registrado');
  tem('Dra. Suellen avisada às'); tem('Esperando você da Dra. Suellen', __els.toast.innerHTML);
  if (!T.estado.log.some(function (l) { return /de que está acabando o estoque de cartuchos de microagulhas \(MMP\): 6 cartuchos de microagulhas\. O pedido foi para o Esperando você dela\./.test(l[1]); })) throw new Error('registro do aviso');
  T.ACOES['avisar-insumo'](botao('avisar-insumo', { id: 'cartucho' }));
  if (T.estado.log.filter(function (l) { return /estoque de cartuchos/.test(l[1]); }).length !== 1) throw new Error('o aviso não se repete');
  location.hash = 'atribuicoes'; tem('insumos acabando no ritmo da agenda · 2 para avisar a Dra. Suellen'); tem('Abrir os insumos');
  location.hash = 'configuracoes'; tem('a gestão é da Fer');
  location.hash = 'copiloto';
});
run('padrão 09/10 · os números das linhas dos grupos batem com a tela de cada grupo', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  location.hash = 'copiloto';
  var casa = __els.tela.innerHTML;
  function numeroDaLinha(gid) { var m = new RegExp('data-gid="' + gid + '"><span class="lg-qtd num" title="[^"]*">(\\d+)<').exec(casa); if (!m) throw new Error('sem a linha de ' + gid); return Number(m[1]); }
  ['urgentes', 'recepcao', 'acompanhamento', 'administrativo'].forEach(function (gid) {
    var n = numeroDaLinha(gid);
    T.ACOES['abrir-grupo'](botao('abrir-grupo', { gid: gid }));
    if (T.visiveis().length !== n) throw new Error(gid + ': a linha diz ' + n + ' e a tela mostra ' + T.visiveis().length);
    tem(n + (n === 1 ? ' tarefa' : ' tarefas') + ' · ');
    T.ACOES['abrir-grupo'](botao('abrir-grupo', { gid: '' }));
  });
  var livres = T.VAGAS.filter(function (v) { return T.estado.vagas[v.id].estado === 'livre'; }).length;
  T.ACOES['abrir-grupo'](botao('abrir-grupo', { gid: 'agenda' }));
  if (numeroDaLinha('agenda') !== T.visiveis().length + livres) throw new Error('agenda: a linha soma as tarefas e os horários livres');
  T.ACOES['abrir-grupo'](botao('abrir-grupo', { gid: '' }));
  var aConfirmar = T.estado.agenda.filter(function (a) { return a.data === T.K[1] && !a.cancelado && a.conf !== 'confirmou'; }).length;
  if (numeroDaLinha('confirmacoes') !== aConfirmar) throw new Error('confirmações: a linha conta quem ainda não respondeu');
});
run('padrão 09/10 · abre no claro, o botão do menu liga a noite, e o número "Horários livres" leva ao bloco', function () {
  if (!document.documentElement.classList.contains('dia')) throw new Error('não abriu no claro');
  T.ACOES.tema(botao('tema'));
  if (document.documentElement.classList.contains('dia')) throw new Error('não foi para a noite');
  T.ACOES.tema(botao('tema'));
  if (!document.documentElement.classList.contains('dia')) throw new Error('não voltou ao claro');
  location.hash = 'copiloto';
  T.ACOES['ir-livres'](botao('ir-livres'));
  tem('id="bloco-livres"'); tem('Horários livres');
});

/* ---------- Copiar e Enviei (Luiz, 15/10/2026): sem integração com o WhatsApp ---------- */
// No DOM falso, as promessas só rodam no fim da suíte: aqui a área de transferência responde na hora.
function SyncP(ok, v) { this.ok = ok; this.v = v; }
SyncP.prototype.then = function (a, b) { try { var r = this.ok ? (a ? a(this.v) : this.v) : (b ? b(this.v) : null); return r && typeof r.then === 'function' ? r : new SyncP(true, r); } catch (e) { return new SyncP(false, e); } };
navigator.clipboard.writeText = function (t) { globalThis.__copiado = t; return new SyncP(true); };
function teclar(k) { (__handlers.keydown || []).forEach(function (fn) { fn({ key: k, target: { closest: function () { return null; } }, preventDefault: function () {} }); }); }
run('copiar: o botão Copiar em toda mensagem, e nada de abrir o WhatsApp', function () {
  var fonte = __HTML;
  ['wa.me', 'abre o WhatsApp', 'abrir o WhatsApp', 'Abrir o WhatsApp', 'Enviar pelo WhatsApp', 'Aprovar abre', 'aprovar abre', 'No sistema, abre', 'Abrir a conversa no WhatsApp', 'Aprovar e enviar', 'Aprovar convite'].forEach(function (x) { if (fonte.indexOf(x) >= 0) throw new Error('sobrou no HTML: ' + x); });
  T.ACOES.reiniciar(botao('reiniciar'));
  location.hash = 'copiloto';
  var c = T.estado.fila.filter(function (x) { return x.tipo === 'mensagem' && x.status === 'pendente' && !x.contato; })[0];
  T.ACOES.selecionar(botao('selecionar', { id: c.id }));
  var h = __els.tela.innerHTML;
  tem('data-acao="copiar-msg" data-fonte="cartao" data-id="' + c.id + '"', h); tem('Copiar</span><span class="tecla">C</span>', h); tem('Enviei<span class="tecla">A</span>', h);
  // Confirmações (inclusive o um por um), convites e a mensagem de marcação.
  T.ACOES.filtrar(botao('filtrar', { atrib: 'confirmacoes' })); tem('data-fonte="conf"');
  T.ACOES['conf-todas'](botao('conf-todas')); tem('Pedidos um por um'); tem('data-fonte="conf"'); naoTem('Copiar a mensagem</button>');
  T.ACOES['conf-seq-parar'](botao('conf-seq-parar'));
  T.ACOES['abrir-vaga'](botao('abrir-vaga', { id: 'v2' })); tem('data-fonte="convite"', painel()); tem('Enviei o convite', painel()); T.ACOES.fechar();
  T.ACOES.reiniciar(botao('reiniciar'));
});
run('copiar: copia o texto que vai ao paciente e não tira a tarefa da lista; Enviei tira', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  location.hash = 'copiloto';
  var c = T.estado.fila.filter(function (x) { return x.tipo === 'mensagem' && x.status === 'pendente' && x.anexos && x.anexos.length && T.textoEnvio(x) !== x.texto; })[0]
    || T.estado.fila.filter(function (x) { return x.tipo === 'mensagem' && x.status === 'pendente'; })[0];
  T.ACOES.selecionar(botao('selecionar', { id: c.id }));
  var antes = T.pendentes().length;
  var b = botao('copiar-msg', { fonte: 'cartao', id: c.id });
  globalThis.__copiado = '';
  T.ACOES['copiar-msg'](b); __flush();
  if (copiado() !== T.textoEnvio(c)) throw new Error('copiou outro texto: ' + copiado().slice(0, 60));
  if (c.status !== 'pendente' || T.pendentes().length !== antes) throw new Error('copiar tirou a tarefa da lista');
  tem('Copiado: cole no WhatsApp da clínica e toque em Enviei.', __els.toast.innerHTML);
  T.aprovarAtual();
  if (c.status !== 'feita' || T.pendentes().length !== antes - 1) throw new Error('Enviei não tirou a tarefa da lista');
  tem('Enviada', __els.toast.innerHTML);
  T.ACOES.reiniciar(botao('reiniciar'));
});
run('copiar: o botão vira "Copiado" com o check por uns 2 segundos', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  var c = T.estado.fila.filter(function (x) { return x.tipo === 'mensagem' && x.status === 'pendente'; })[0];
  var b = botao('copiar-msg', { fonte: 'cartao', id: c.id });
  b.innerHTML = 'Copiar';
  T.ACOES['copiar-msg'](b);
  if (b.innerHTML.indexOf('Copiado') < 0 || !b.classList.contains('botao-copiar--ok')) throw new Error('o botão não virou Copiado: ' + b.innerHTML);
  __flush();
  if (b.innerHTML !== 'Copiar' || b.classList.contains('botao-copiar--ok')) throw new Error('o botão não voltou: ' + b.innerHTML);
});
run('copiar: tecla C copia a mensagem da tarefa aberta, sem marcar como enviada', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  location.hash = 'copiloto';
  var c = T.estado.fila.filter(function (x) { return x.tipo === 'mensagem' && x.status === 'pendente'; })[0];
  T.ACOES.selecionar(botao('selecionar', { id: c.id }));
  globalThis.__copiado = '';
  teclar('c');
  if (copiado() !== T.textoEnvio(c)) throw new Error('a tecla C não copiou: ' + copiado().slice(0, 60));
  if (c.status !== 'pendente') throw new Error('a tecla C marcou como enviada');
  teclar('a');
  if (c.status !== 'feita') throw new Error('a tecla A (Enviei) não marcou');
  // Nas confirmações, a tecla C copia o pedido do próximo paciente, e a A marca esse pedido como enviado.
  T.ACOES.filtrar(botao('filtrar', { atrib: 'confirmacoes' }));
  var lote = T.cartao('confirmacoes');
  T.ACOES.selecionar(botao('selecionar', { id: lote.id }));
  globalThis.__copiado = '';
  teclar('c');
  var a = itemDe('1-ana-1000-vinícius');
  if (copiado().indexOf(T.textoConfirmacao(a).slice(0, 30)) < 0) throw new Error('a tecla C não copiou o pedido do Vinícius: ' + copiado().slice(0, 60));
  if (a.conf !== 'a-enviar') throw new Error('copiar o pedido não é enviar');
  teclar('a');
  if (a.conf !== 'enviada') throw new Error('Enviei não marcou o pedido');
  T.ACOES.reiniciar(botao('reiniciar'));
});
run('copiar: mensagem editada copia o texto editado; com promessa, não copia nem sai', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  location.hash = 'copiloto';
  var c = T.estado.fila.filter(function (x) { return x.tipo === 'mensagem' && x.status === 'pendente' && !x.fixo; })[0];
  T.ACOES.selecionar(botao('selecionar', { id: c.id }));
  T.editarAtual();
  tem('Enviei<span class="tecla">A</span>'); tem('data-fonte="cartao"');
  var ed = getEl('editor-' + c.id);
  ed.value = 'Oi! Texto editado pela Fer, sem nada de mais.';
  globalThis.__copiado = '';
  T.ACOES['copiar-msg'](botao('copiar-msg', { fonte: 'cartao', id: c.id }));
  if (copiado().indexOf('Texto editado pela Fer') < 0) throw new Error('não copiou o texto editado: ' + copiado());
  if (c.status !== 'pendente') throw new Error('copiar marcou como enviada');
  ed.value = 'Oi! Com o tratamento, o resultado é garantido.';
  globalThis.__copiado = '';
  T.ACOES['copiar-msg'](botao('copiar-msg', { fonte: 'cartao', id: c.id }));
  if (copiado()) throw new Error('copiou a mensagem com promessa');
  if (__els.toast.classList.contains('toast--alerta') !== true) throw new Error('sem o aviso da promessa');
  T.ACOES.reiniciar(botao('reiniciar'));
});
run('copiar: plano B, se a área de transferência falhar, avisa para usar Ctrl+C e não marca nada', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  var c = T.estado.fila.filter(function (x) { return x.tipo === 'mensagem' && x.status === 'pendente'; })[0];
  var velho = navigator.clipboard.writeText;
  navigator.clipboard.writeText = function () { return new SyncP(false, new Error('bloqueado')); };
  try {
    T.ACOES['copiar-msg'](botao('copiar-msg', { fonte: 'cartao', id: c.id }));
    __flush();
  } finally { navigator.clipboard.writeText = velho; }
  tem('Não deu para copiar sozinho: o texto está selecionado, use Ctrl+C.', __els.toast.innerHTML);
  if (c.status !== 'pendente') throw new Error('marcou como enviada');
});
run('copiar: confirmação, convite e o celular da ficha', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  var a = itemDe('1-ana-1000-vinícius');
  copiarConf(a.id);
  if (copiado().indexOf(T.textoConfirmacao(a)) !== 0) throw new Error('não copiou o pedido');
  if (a.conf !== 'a-enviar') throw new Error('copiar o pedido não é enviar');
  globalThis.__copiado = '';
  T.ACOES['copiar-msg'](botao('copiar-msg', { fonte: 'convite', vaga: 'v2', pessoa: 'debora' }));
  if (copiado() !== T.convite('debora', T.VAGA.v2)) throw new Error('não copiou o convite');
  if (T.estado.vagas.v2.estado !== 'livre') throw new Error('copiar o convite não é enviar');
  T.ACOES.convidar(botao('convidar', { vaga: 'v2', pessoa: 'debora' }));
  if (T.estado.vagas.v2.estado !== 'convidado') throw new Error('Enviei o convite não marcou');
  globalThis.__copiado = '';
  T.ACOES.ficha(botao('ficha', { nome: 'Lucas Fontana' }));
  T.ACOES['ficha-whats'](botao('ficha-whats'));
  __flush();
  if (copiado() !== T.pacientePorNome('Lucas Fontana').resp.cel) throw new Error('não copiou o celular da mãe: ' + copiado());
  T.ACOES.fechar();
  T.ACOES.reiniciar(botao('reiniciar'));
});
'''

ctx = quickjs.Context()
ctx.eval(SHIM)
ctx.eval(script)
ctx.eval('var __HTML = ' + json.dumps(html) + ';')
ctx.eval(TESTES)
while ctx.execute_pending_job():
    pass
print(ctx.eval('__out.join("\\n")'))
