"""Roda o JavaScript do copiloto da Dra. Monielle num DOM falso e exercita telas e ações."""
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, definirModo, resumoDoDia, '
           'PAC, CONSULTA, CONSULTAS, TELAS, HOJE, get modo() { return modo; } };\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', 'horaAparelho', 'painel', 'veu', 'toast', 'botaoTema', 'telaApp', 'textoPrevia'];
['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.innerWidth = 1440;
window.innerHeight = 900;
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 tem(trecho, onde) { var h = onde === undefined ? __els.tela.innerHTML : onde; if (h.indexOf(trecho) < 0) throw new Error('não achei: ' + trecho); }
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 painel() { return __els.painel.innerHTML; }
function igual(a, b, msg) { if (a !== b) throw new Error((msg || 'valor') + ': esperava ' + b + ', veio ' + a); }
function ctxCom(mapa, dados) { var c = new El('ctx'); c.dataset = dados || {}; c.querySelector = function (s) { return mapa[s] || null; }; c.querySelectorAll = function (s) { return mapa['TODOS ' + s] || []; }; return c; }
function campo(v) { var e = new El('campo'); e.value = v; return e; }
function opc(v) { var e = new El('opcao'); e.dataset = { valor: v }; return e; }
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; return null; };
  return e;
}
function ir(h) { location.hash = h; }
function reiniciar() { T.ACOES.reiniciar(botao('reiniciar')); }
function dias(n) { return n === 1 ? '1 dia' : n + ' dias'; }
function haDias(n) { return n === 0 ? 'hoje' : n === 1 ? 'ontem' : 'há ' + n + ' dias'; }
function trecho(html, ini, fim) { var i = html.indexOf(ini); if (i < 0) throw new Error('não achei o começo: ' + ini); var j = html.indexOf(fim, i); return html.slice(i, j < 0 ? undefined : j); }
// A agenda em grupos (13/10/2026): abre um grupo; com '', volta à página da agenda.
function grupo(g) { T.ACOES['abrir-grupo'](botao('abrir-grupo', { gid: g })); }
// O número da linha de um grupo, na página da agenda.
function qtd(g) { var m = new RegExp('data-gid="' + g + '"><span class="lg-qtd num">(\\d+)<').exec(__els.tela.innerHTML); if (!m) throw new Error('sem a linha do grupo ' + g); return Number(m[1]); }
// A linha de um grupo na página (o número do dia também tem data-gid, por isso o começo é o número da linha).
function linha(g) { return trecho(__els.tela.innerHTML, 'data-gid="' + g + '"><span class="lg-qtd', '</button></li>'); }
// O botão inteiro de um número do dia, da abertura do <button> ao fechamento.
function kpi(rotulo) { var s = trecho(__els.tela.innerHTML, 'class="dia-kpis"', '</section>'); var i = s.indexOf(rotulo); if (i < 0) throw new Error('sem o número ' + rotulo); var a = s.lastIndexOf('<button', i); return s.slice(a, s.indexOf('</button>', i)); }


// A Dra. Monielle é um processo à parte (Luiz, 07/10/2026): com ela, o sistema trabalha só com a agenda. Ela não registra
// nada, e os textos para pacientes passaram para a aprovação só da Dra. Suellen.
// Desde 13/10/2026 a agenda é a página no padrão da Fer: a saudação com os fatos, os números do dia, um grupo por linha e,
// ao lado, a agenda de hoje hora a hora e os próximos dias.
run('abre na agenda, no padrão: a saudação com os fatos, os números do dia e as três linhas', function () {
  tem('Bom dia, Dra. Monielle.'); tem('Preparado às 06:00 · Ter, 06/10');
  tem('Hoje você atende <strong>1 paciente</strong>: Odete Prado, às 15:00, retorno a seu pedido.');
  tem('Na quinta, às 15:00: primeira consulta de Lia Fagundes, encaminhada pela Dra. Suellen.');
  tem('O próximo horário livre é na quinta, às 14:00: a Fer encaixa.'); naoTem('texto espera'); naoTem('o seu OK');
  // Os cinco números do dia, cada um levando para onde se vê mais.
  tem('<strong class="dia-kpi-v num">1</strong><span>à tarde</span>', kpi('Consultas hoje')); tem('Odete Prado, 15:00', kpi('Consultas hoje')); tem('dia-kpi--destaque', kpi('Consultas hoje')); tem('data-alvo="h-agenda"', kpi('Consultas hoje'));
  tem('<strong class="dia-kpi-v num">1</strong><span>em 7 dias</span>', kpi('Primeiras consultas')); tem('data-gid="primeiras"', kpi('Primeiras consultas'));
  tem('<strong class="dia-kpi-v num">1</strong><span>em 7 dias</span>', kpi('Retornos')); tem('data-gid="retornos"', kpi('Retornos'));
  tem('<strong class="dia-kpi-v">Quinta</strong><span class="num">08/10 · 14:00</span>', kpi('Próximo horário livre')); tem('data-alvo="h-proximos"', kpi('Próximo horário livre'));
  tem('<strong class="dia-kpi-v num">0</strong>', kpi('Observações a escrever')); tem('data-gid="observacoes"', kpi('Observações a escrever'));
  // As três linhas, uma por grupo, com o número, o nome, quando e os tipos.
  ['primeiras', 'retornos', 'observacoes'].forEach(function (g) { tem('data-gid="' + g + '"'); });
  igual(qtd('primeiras'), 1, 'primeiras'); igual(qtd('retornos'), 1, 'retornos'); igual(qtd('observacoes'), 0, 'observações');
  tem('Primeiras consultas', linha('primeiras')); tem('<span class="lg-quando">Quinta</span>', linha('primeiras')); tem('Lia Fagundes, 15:00', linha('primeiras')); tem('A Fer confirma na véspera', linha('primeiras')); tem('class="lg lg--tem" data-acao="abrir-grupo" data-gid="primeiras"');
  tem('Retornos', linha('retornos')); tem('<span class="lg-quando">Hoje</span>', linha('retornos')); tem('Odete Prado, 15:00', linha('retornos')); tem('Confirmada', linha('retornos')); tem('Antes e agora', linha('retornos')); tem('class="lg lg--tem" data-acao="abrir-grupo" data-gid="retornos"');
  tem('Observações a escrever', linha('observacoes')); tem('<span class="lg-quando">Depois da consulta</span>', linha('observacoes')); tem('Depois da consulta das 15:00', linha('observacoes')); tem('class="lg" data-acao="abrir-grupo" data-gid="observacoes"');
  tem('<h2 id="h-grupos">Sua agenda</h2><span class="detalhe">2 consultas em 7 dias</span>');
  var h = __els.tela.innerHTML;
  ['Alertas esperando', 'abrir-alerta', 'responder-alerta', 'Encaminhados', 'Sessões em pausa', 'Pedidos de exame', 'Exames de sangue', 'Registrar', 'registrar-consulta', 'Os últimos 7 dias', 'Pergunte ao copiloto', 'class="alerta'].forEach(function (x) { naoTem(x); });
  tem('A IA só junta fatos.'); igual((h.match(/A IA só junta fatos/g) || []).length, 1, 'a nota da IA aparece uma vez');
});
run('a contagem de cada linha bate com a tela do grupo, e Voltar à agenda volta', function () {
  ['primeiras', 'retornos', 'observacoes'].forEach(function (g) {
    grupo(''); var n = qtd(g);
    grupo(g); igual(T.estado.grupo, g, 'grupo aberto');
    tem('<strong class="num">' + n + '</strong>'); tem('class="grupo-trilha"'); tem('>Agenda</button>'); tem('Voltar à agenda'); tem('class="grupo-titulo"'); naoTem('Bom dia, Dra. Monielle.'); naoTem('class="dia-kpis"');
  });
  grupo(''); if (T.estado.grupo !== null) throw new Error('não voltou à agenda'); tem('Bom dia, Dra. Monielle.');
  grupo('nada'); if (T.estado.grupo !== null) throw new Error('grupo desconhecido devia voltar à agenda'); tem('Bom dia, Dra. Monielle.');
});
run('primeiras consultas: quem encaminhou e quando, o nome abre a ficha e a consulta abre o resumo', function () {
  grupo('primeiras');
  tem('<strong class="num">1</strong> consulta'); tem('Quem a Dra. Suellen encaminhou ao fechar o protocolo.'); tem('>Quinta</p>');
  tem('data-acao="abrir-ficha" data-pid="lia">Lia Fagundes</button>'); tem('15:00'); tem('Primeira consulta'); tem('A Fer confirma na véspera');
  tem('Quinta, 08/10 · Encaminhada pela Dra. Suellen ao fechar o protocolo, há 5 dias. A Fer marcou a primeira consulta.');
  tem('Avaliação com a Dra. Suellen há 5 dias: fechou o protocolo. As sessões com a Ana Carolina ainda não começaram.');
  tem('data-acao="abrir-consulta" data-id="lia">Resumo antes de entrar'); naoTem('Odete Prado'); naoTem('Escrever as observações');
  T.ACOES['abrir-consulta'](botao('abrir-consulta', { id: 'lia' }));
  tem('Primeira consulta · quinta, 15:00', painel()); tem('Por que está na sua agenda', painel());
  T.ACOES.fechar();
  T.ACOES['abrir-ficha'](botao('abrir-ficha', { pid: 'lia' }));
  tem('Ficha resumida · a sua visão', painel()); tem('Lia Fagundes', painel());
  T.ACOES.fechar(); grupo('');
});
run('retornos: os que ela pediu direto à Fer', function () {
  grupo('retornos');
  tem('<strong class="num">1</strong> consulta'); tem('Os retornos que você pediu direto à Fer.'); tem('>Hoje</p>');
  tem('data-acao="abrir-ficha" data-pid="odete">Odete Prado</button>'); tem('Retorno'); tem('Confirmada');
  tem('Hoje, 06/10 · Retorno que você pediu direto à Fer. Ela marcou há 8 dias.');
  tem('Em tratamento: 3 de 6 sessões feitas. A última foi há 12 dias, com a Ana Carolina.'); tem('Próxima sessão: sessão 4 de 6, na sexta, às 10:00.');
  naoTem('Lia Fagundes'); naoTem('Escrever as observações');
  grupo('');
});
run('observações a escrever: só as consultas de hoje já atendidas, e o botão abre a ficha no campo', function () {
  reiniciar();
  grupo('observacoes');
  tem('<strong class="num">0</strong> a escrever'); tem('>Depois da consulta</p>'); tem('Você escreve; a equipe vê, e a IA não lê.');
  // Às 09:20 a consulta das 15:00 ainda não aconteceu: aparece como "depois da consulta", com o botão à mão.
  tem('data-acao="abrir-ficha" data-pid="odete" data-foco="obs">Odete Prado</button>'); tem('Depois da consulta, às 15:00'); naoTem('chip--atencao');
  tem('data-acao="abrir-ficha" data-pid="odete" data-foco="obs">'); tem('Escrever as observações'); naoTem('Lia Fagundes');
  T.ACOES['abrir-ficha'](botao('abrir-ficha', { pid: 'odete', foco: 'obs' }));
  tem('Suas observações', painel()); tem('id="obs-moni"', painel()); tem('Salvar observações', painel());
  igual(T.estado.acessos.length, 1, 'a abertura pela observação também fica registrada');
  // Salvou hoje: a consulta sai da lista, e a página se atualiza por trás do painel.
  T.ACOES['obs-salvar'](botao('obs-salvar', { pid: 'odete' }, ctxCom({ '#obs-moni': campo('Retorno em 90 dias.') })));
  tem('Nada por enquanto. Depois de cada consulta de hoje, ela aparece aqui até você escrever as observações na ficha.'); naoTem('Odete Prado');
  T.ACOES.fechar();
  grupo(''); tem('Nada por enquanto.', linha('observacoes')); naoTem('Depois da consulta das 15:00', linha('observacoes'));
  reiniciar(); tem('Depois da consulta das 15:00', linha('observacoes'));
});
run('a agenda de hoje, hora a hora, com o agora no alto e os horários livres', function () {
  reiniciar(); grupo('');
  var a = trecho(__els.tela.innerHTML, 'id="h-agenda"', '</section>');
  tem('Terça, 06/10 · 1 consulta, 3 horários livres', a); tem('id="rolagem-agenda"', a);
  tem('<li class="agora-marca"><span class="num">09:20</span> · agora</li>', a);
  ['14:00', '16:00', '17:00'].forEach(function (h) { tem('<span class="linha-hora num">' + h + '</span>', a); }); tem('<span class="linha-hora">15:00</span>', a);
  igual((a.match(/>Livre</g) || []).length, 3, 'três horários livres');
  tem('data-acao="abrir-consulta" data-id="odete"', a); tem('Odete Prado', a); tem('Retorno · a seu pedido', a); tem('Confirmada', a);
  if (a.indexOf('agora-marca') > a.indexOf('>14:00<')) throw new Error('às 09:20 o agora vem antes das 14:00');
  if (a.indexOf('>14:00<') > a.indexOf('>15:00<') || a.indexOf('>16:00<') > a.indexOf('>17:00<')) throw new Error('a agenda segue a ordem das horas');
  naoTem('Lia Fagundes', a);
  T.ACOES['abrir-consulta'](botao('abrir-consulta', { id: 'odete' }));
  tem('Retorno · hoje, 15:00', painel());
  T.ACOES.fechar();
});
run('próximos dias: as consultas, os horários livres e o próximo horário livre em destaque', function () {
  grupo('');
  var p = trecho(__els.tela.innerHTML, 'id="h-proximos"', '</section>');
  tem('Você atende às terças e quintas, à tarde.', p);
  tem('<div class="proximo-livre">', p); tem('Próximo horário livre</span><strong>Quinta, 14:00</strong>', p); tem('08/10 · a Fer encaixa quem fechou o protocolo e espera a primeira consulta com você, ou o retorno que você pedir direto a ela.', p);
  tem('Quinta, 08/10', p); tem('<span class="detalhe">1 consulta: Lia Fagundes, 15:00</span>', p);
  tem('Terça, 13/10', p); tem('<span class="detalhe">Sem consulta marcada</span>', p); tem('Quinta, 15/10', p); tem('Terça, 20/10', p);
  igual((p.match(/class="linha linha--fixa"/g) || []).length, 4, 'quatro dias de atendimento');
  tem('3 livres', p); tem('4 livres', p); naoTem('Cheia', p); naoTem('Odete Prado', p);
  tem('Conta a partir de amanhã; os horários livres de hoje estão na sua agenda, acima.', p);
  if (p.indexOf('Quinta, 08/10') > p.indexOf('Terça, 13/10')) throw new Error('os dias seguem a ordem');
});
run('os números do dia levam para onde se vê mais', function () {
  grupo('retornos'); tem('Voltar à agenda');
  T.ACOES.ir(botao('ir', { tela: 'agenda', alvo: 'h-agenda' }));
  if (T.estado.grupo !== null) throw new Error('o número do dia devia voltar à agenda'); tem('Bom dia, Dra. Monielle.'); tem('id="h-agenda"');
  T.ACOES.ir(botao('ir', { tela: 'agenda', alvo: 'h-proximos' })); tem('id="h-proximos"');
});
run('como a clínica trabalha com ela: só a agenda', function () {
  var c = trecho(__els.tela.innerHTML, 'id="h-como"', '</section>');
  tem('A Dra. Suellen encaminha quem fecha o protocolo, e a Fer marca a primeira consulta aqui, na sua agenda.', c);
  tem('O sistema não pede registro nenhum: o controle é seu.', c); tem('você pede direto à Fer', c);
  tem('Exame de sangue e pedido de exame que chegam na clínica, a Fer passa direto para você.', c);
  tem('Sintoma ou dúvida sobre remédio, o paciente fala direto com você; mensagem que chegar na clínica, a Fer passa como chegou. Nada disso fica registrado no sistema.', c);
  tem('Pausa nas sessões, você combina direto com a Ana Carolina e a Fer.', c); tem('Os textos fixos que a equipe manda aos pacientes, quem aprova é a Dra. Suellen.', c); naoTem('em Textos', c); naoTem('guarda só que passou', c);
  tem('a Fer encaixa quem fechou o protocolo e espera a primeira consulta com você');
});
run('datas da história caem em dia útil', function () {
  var util = function (d) { var w = new Date(d).getDay(); return w !== 0 && w !== 6; };
  Object.keys(T.PAC).forEach(function (k) {
    var p = T.PAC[k];
    p.sessoes.concat(p.proximas).forEach(function (s) { if (!util(s.data)) throw new Error(p.nome + ': ' + s.o + ' no fim de semana'); });
    p.homecare.forEach(function (h) { if (!util(h.data)) throw new Error(p.nome + ': entrega no fim de semana'); });
  });
});
run('retorno: o resumo antes de entrar, sem nada para registrar', function () {
  reiniciar();
  T.ACOES['abrir-consulta'](botao('abrir-consulta', { id: 'odete' }));
  var p = painel();
  tem('Odete Prado', p); tem('Retorno · hoje, 15:00', p);
  tem('Por que está na sua agenda', p); tem('Retorno que você pediu direto à Fer. Ela marcou há 8 dias.', p);
  tem('Em tratamento: 3 de 6 sessões feitas. A última foi há 12 dias, com a Ana Carolina.', p); tem('Próxima sessão: sessão 4 de 6, na sexta, às 10:00.', p);
  tem('Home care: Capilare Nutri, entregue em', p);
  tem('Exame de sangue: pedido em', p); tem('passado a você pela Fer em', p); tem('O arquivo do exame está no Clinicorp.', p); naoTem('visto por você', p);
  tem('Home care com remédio', p); tem('Nenhum registrado aqui.', p);
  tem('Você fala com ela direto. Melhor horário: à tarde.', p); tem('href="tel:+5541900000755"', p);
  tem('Antes e agora, só para ver', p);
  tem('A prescrição continua no seu sistema. Aqui, você escreve na ficha as observações do atendimento', p);
  ['Depois da consulta', 'registrar-consulta', 'Retorno com você', 'Em pausa', 'Volta para a Dra. Suellen', 'Pedi exames', 'Registrar<'].forEach(function (x) { naoTem(x, p); });
  T.ACOES.fechar();
});
run('primeira consulta: encaminhada pela Dra. Suellen quando a paciente fechou', function () {
  T.ACOES['abrir-consulta'](botao('abrir-consulta', { id: 'lia' }));
  var p = painel();
  tem('Primeira consulta · quinta, 15:00', p);
  tem('Encaminhada pela Dra. Suellen ao fechar o protocolo, há 5 dias. A Fer marcou a primeira consulta.', p);
  tem('Avaliação com a Dra. Suellen há 5 dias: fechou o protocolo. As sessões com a Ana Carolina ainda não começaram.', p);
  tem('Próxima sessão: sessão 1 de 6, na quarta, 14/10, às 09:00.', p); tem('Nenhum home care registrado.', p);
  tem('Exame de sangue: pedido em', p); tem('ainda não entregue', p); tem('Foto da estação', p);
  T.ACOES.fechar();
});
run('ficha resumida: só o que a Dra. Monielle vê', function () {
  reiniciar();
  T.ACOES['abrir-consulta'](botao('abrir-consulta', { id: 'odete' }));
  T.ACOES['abrir-ficha'](botao('abrir-ficha', { pid: 'odete', volta: 'consulta:odete' }));
  var p = painel();
  tem('Ficha resumida · a sua visão', p); tem('Contato e preferências', p); tem('(41) 90000-0755', p); tem('Confirmado', p); tem('À tarde', p);
  tem('Agenda e sessões', p); tem('3 de 6 sessões feitas', p); tem('Retorno, 15:00 · Dra. Monielle', p); tem('Sessão 4 de 6, 10:00 · Ana Carolina', p); tem('Primeira consulta · Dra. Monielle', p);
  tem('Home care', p); tem('Capilare Nutri', p); tem('Fotos', p); tem('Foto da estação', p);
  ['Alertas', 'Em pausa', 'Origem', 'Indicação', 'Pagamento', 'R$', 'Termo de', 'Parceira', 'Vim pel'].forEach(function (x) { naoTem(x, p); });
  tem('Na sua visão não aparecem origem, termos e pagamentos.', p); tem('A prescrição continua no seu sistema; as suas observações ficam aqui, para a equipe.', p); tem('Esta abertura ficou registrada: Dra. Monielle, hoje, às 09:20.', p);
  igual(T.estado.acessos.length, 1, 'acesso registrado');
  if (!T.estado.log.some(function (l) { return l[1] === 'A Dra. Monielle abriu a ficha de Odete Prado.'; })) throw new Error('log sem a abertura');
  tem('Voltar à consulta', p);
  T.ACOES.voltar(botao('voltar', { volta: 'consulta:odete' }));
  tem('Por que está na sua agenda', painel());
  T.ACOES.fechar();
});
run('textos do copiloto: só fatos, sem alerta, gravidade nem conduta, na página e nos grupos', function () {
  reiniciar();
  ir('agenda');
  var textos = [T.resumoDoDia(), __els.tela.innerHTML];
  ['primeiras', 'retornos', 'observacoes'].forEach(function (g) { grupo(g); textos.push(__els.tela.innerHTML); });
  grupo('');
  ['odete', 'lia'].forEach(function (id) { T.ACOES['abrir-consulta'](botao('abrir-consulta', { id: id })); textos.push(painel()); });
  var proibidas = /\bgrave\b|gravíssim|urgente|recomendo|recomendamos|sugiro|sugerimos|deve usar|deve tomar|suspender|diagnóstico de|provável|prioridade|gravidade/i;
  textos.forEach(function (t) { var m = proibidas.exec(t); if (m) throw new Error('texto clínico do copiloto: ' + m[0]); });
  // Nada de alerta, exame a cobrar, encaminhado, lido ou respondido: com ela, o sistema só trabalha com a agenda.
  textos.slice(1).forEach(function (t) { ['Alerta', 'alerta-topo', 'Lido', 'Respondido', 'Encaminhados', 'Pedidos de exame', 'Textos para o seu OK'].forEach(function (x) { if (t.indexOf(x) >= 0) throw new Error('não devia aparecer: ' + x); }); });
  T.ACOES.fechar();
});
run('Luiz, 07/10 · uma tela só, a agenda, e os endereços antigos abrem nela', function () {
  igual(Object.keys(T.TELAS).join(','), 'agenda', 'telas');
  ['alertas', 'consultas', 'encaminhados', 'semana', 'revisar', 'nada'].forEach(function (t) { ir(t); tem('Bom dia, Dra. Monielle.'); naoTem('Textos para o seu OK.'); naoTem('Campanha do emagrecimento'); });
});
run('prévia de iPhone e tema: abre no claro, e o botão liga o modo noturno', function () {
  T.definirModo('iphone');
  if (!document.documentElement.classList.contains('modo-iphone')) throw new Error('sem iPhone');
  if (!document.documentElement.classList.contains('preview')) throw new Error('sem prévia');
  // No iPhone a página é a mesma: as três linhas e a agenda de hoje (a tela cheia e a prévia respondem à largura por CSS).
  ['primeiras', 'retornos', 'observacoes'].forEach(function (g) { tem('data-gid="' + g + '"'); }); tem('id="h-agenda"'); tem('id="h-proximos"');
  grupo('retornos'); tem('Voltar à agenda'); grupo('');
  T.definirModo('tela');
  if (document.documentElement.classList.contains('preview')) throw new Error('ficou em prévia');
  ir('iphone');
  igual(T.modo, 'iphone', 'hash de prévia');
  igual(location.hash, '#agenda', 'volta para a agenda');
  T.definirModo('tela');
  // Desde 09/10/2026, abre no claro, como a Fer e a doutora; o botão do menu liga o modo noturno.
  if (!document.documentElement.classList.contains('dia')) throw new Error('não abriu no claro');
  T.ACOES.tema(botao('tema'));
  if (!document.documentElement.classList.contains('noite')) 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 para o dia');
});
run('Esc fecha o painel', function () {
  T.ACOES['ver-log'](botao('ver-log'));
  tem('O que o copiloto fez', painel()); tem('Preparei o resumo do retorno de Odete Prado', painel()); tem('a primeira consulta de Lia Fagundes', painel());
  __handlers.keydown[0]({ key: 'Escape', target: { closest: function () { return null; } }, preventDefault: function () {} });
  if (!__els.painel.hidden) throw new Error('não fechou');
});
run('reiniciar volta tudo ao começo', function () {
  grupo('primeiras');
  T.ACOES['abrir-ficha'](botao('abrir-ficha', { pid: 'lia' }));
  igual(T.estado.acessos.length, 1, 'abriu a ficha');
  reiniciar();
  igual(T.estado.acessos.length, 0, 'acessos');
  igual(T.estado.log.length, 2, 'registro');
  if (T.estado.grupo !== null) throw new Error('o grupo devia fechar'); tem('Bom dia, Dra. Monielle.');
  ir('agenda');
});
run('noite 5: dias fixos de atendimento, terça e quinta, e as consultas nesses dias', function () {
  T.ACOES.reiniciar(botao('reiniciar'));
  ir('agenda');
  tem('Você atende às terças e quintas, à tarde.');
  T.CONSULTAS.forEach(function (c) { if ([2, 4].indexOf(c.data.getDay()) < 0) throw new Error('consulta fora dos dias dela: ' + c.id); });
  if (T.CONSULTA.lia.data <= T.CONSULTA.odete.data) throw new Error('a Lia vem depois da Odete');
});
run('Luiz, 07/10 · a Dra. Monielle à parte: não responde alerta nem aprova texto', function () {
  ['responder-alerta', 'abrir-alerta', 'liberar', 'ja-liguei', 'exame-visto', 'pedido-feito', 'registrar-consulta', 'opcao', 'aprovar-texto', 'ajuste-texto', 'aba-lado', 'chat'].forEach(function (a) {
    if (T.ACOES[a]) throw new Error('ação que registra ainda existe: ' + a);
  });
  ['abrir-consulta', 'abrir-ficha', 'abrir-grupo', 'ir', 'obs-salvar'].forEach(function (a) { if (!T.ACOES[a]) throw new Error('faltou: ' + a); });
  igual(Object.keys(T.estado).sort().join(','), 'acessos,grupo,log', 'o estado guarda só os acessos, o registro e o grupo aberto');
  // A primeira consulta vem do encaminhamento da Dra. Suellen; o retorno, de um pedido dela direto à Fer.
  igual(T.CONSULTA.lia.porque.de, 'suellen', 'primeira consulta'); igual(T.CONSULTA.odete.porque.de, 'voce', 'retorno');
});

run('Luiz, 08/10 · observações da Dra. Monielle na ficha: ela escreve, a equipe vê, e a IA não lê', function () {
  T.ACOES['abrir-ficha'](botao('abrir-ficha', { pid: 'odete', volta: 'consulta:odete' }));
  var p = painel();
  tem('Suas observações', p); tem('ferritina baixa', p); tem('a Fer, a Ana Carolina e a Dra. Suellen veem na ficha. A IA não lê.', p);
  T.ACOES['obs-salvar'](botao('obs-salvar', { pid: 'odete' }, ctxCom({ '#obs-moni': campo('Retorno em 90 dias, com novo exame.') })));
  igual(T.estado.obs.odete.texto, 'Retorno em 90 dias, com novo exame.', 'observação salva');
  if (!T.estado.log.some(function (l) { return l[1].indexOf('Você salvou as observações de Odete Prado') === 0; })) throw new Error('log sem as observações');
});

'''

ctx = quickjs.Context()
ctx.eval(SHIM)
ctx.eval(script)
ctx.eval(TESTES)
while ctx.execute_pending_job():
    pass
saida = ctx.eval('__out.join("\\n")')
print(saida)
falhas = saida.count('FALHA')
print(f'{saida.count("ok    ")} ok, {falhas} falha(s)')
sys.exit(1 if falhas else 0)
