Files
RedatorSD/prototipo-criador-de-demanda.html
T

720 lines
35 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Redator de Demandas — protótipo</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter+Tight:wght@400;500;600;700&family=Newsreader:ital,opsz,wght@0,6..72,400;0,6..72,500;0,6..72,600;1,6..72,400&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
<style>
:root{
--ink:#0f2338;
--ink-2:#3c5470;
--ink-3:#7a8ca1;
--canvas:#e7eaef;
--panel:#ffffff;
--paper:#fdfdfa;
--line:#d6dbe3;
--line-2:#eceff4;
--teal:#0b8fa8;
--blue:#1f6fd0;
--amber:#a8701a;
--amber-bg:#fdf4e3;
--rose:#9e2f42;
--rose-bg:#fbeef0;
--green:#1f7a52;
--green-bg:#eaf4ee;
--r:3px;
}
*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%}
body{
background:var(--canvas);
color:var(--ink);
font-family:'Inter Tight',-apple-system,BlinkMacSystemFont,sans-serif;
font-size:14px;
line-height:1.5;
-webkit-font-smoothing:antialiased;
overflow:hidden;
}
button{font-family:inherit;cursor:pointer;border:none;background:none;color:inherit}
:focus-visible{outline:2px solid var(--blue);outline-offset:2px}
@media(prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}
/* ---------- shell ---------- */
.app{display:grid;grid-template-rows:auto 1fr;height:100vh}
.topbar{
display:flex;align-items:center;gap:14px;
padding:0 20px;height:52px;
background:var(--ink);color:#fff;
border-bottom:1px solid #06172a;
}
.logo{display:flex;align-items:center;gap:9px;font-weight:600;letter-spacing:-.01em}
.logo svg{flex:none}
.topbar .sep{width:1px;height:20px;background:rgba(255,255,255,.18)}
.topbar .ctx{font-size:12.5px;color:#9fb4ca}
.topbar .ctx b{color:#dce8f3;font-weight:500}
.topbar .right{margin-left:auto;display:flex;align-items:center;gap:10px}
.pill{
font-size:11px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;
padding:4px 9px;border-radius:2px;background:rgba(255,255,255,.1);color:#bcd3e8;
}
.btn-reset{font-size:12.5px;color:#9fb4ca;padding:5px 10px;border:1px solid rgba(255,255,255,.2);border-radius:var(--r)}
.btn-reset:hover{color:#fff;border-color:rgba(255,255,255,.45)}
.main{display:grid;grid-template-columns:1fr 1fr;overflow:hidden}
/* ---------- coluna esquerda: conversa ---------- */
.left{display:flex;flex-direction:column;background:var(--canvas);border-right:1px solid var(--line);overflow:hidden}
.left-head{
padding:13px 26px;border-bottom:1px solid var(--line);
display:flex;align-items:baseline;gap:10px;background:var(--canvas);
}
.eyebrow{font-size:10.5px;font-weight:700;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3)}
.left-head .step{margin-left:auto;font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--ink-3)}
.stream{flex:1;overflow-y:auto;padding:26px 26px 8px;scroll-behavior:smooth}
.stream::-webkit-scrollbar{width:9px}
.stream::-webkit-scrollbar-thumb{background:#c3cad4;border-radius:9px;border:3px solid var(--canvas)}
.msg{margin-bottom:22px;animation:rise .34s cubic-bezier(.2,.7,.3,1) both}
@keyframes rise{from{opacity:0;transform:translateY(9px)}to{opacity:1;transform:none}}
.who{
font-size:10.5px;font-weight:700;letter-spacing:.11em;text-transform:uppercase;
color:var(--ink-3);margin-bottom:7px;display:flex;align-items:center;gap:7px;
}
.who i{width:5px;height:5px;border-radius:50%;background:var(--teal);font-style:normal}
.msg.user .who i{background:var(--ink-3)}
.bubble{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);padding:15px 17px;font-size:14px;line-height:1.62}
.msg.user .bubble{background:transparent;border-style:dashed;border-color:#c3cad4;color:var(--ink-2)}
.bubble p+p{margin-top:10px}
.bubble strong{font-weight:600}
.bubble .quiet{color:var(--ink-2)}
/* fonte da base de conhecimento */
.sources{margin-top:13px;padding-top:12px;border-top:1px solid var(--line-2)}
.sources-h{font-size:10.5px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);margin-bottom:9px;display:flex;align-items:center;gap:6px}
.src{
display:flex;gap:10px;padding:8px 10px;border-radius:2px;background:#f4f6f9;
border-left:2px solid var(--teal);margin-bottom:6px;
}
.src:last-child{margin-bottom:0}
.src .id{font-family:'JetBrains Mono',monospace;font-size:11px;font-weight:700;color:var(--teal);flex:none;padding-top:1px}
.src .txt{font-size:12.5px;line-height:1.45;color:var(--ink-2)}
.src .txt b{color:var(--ink);font-weight:600}
/* alerta */
.flag{margin-top:13px;padding:11px 13px;background:var(--amber-bg);border-left:2px solid var(--amber);font-size:12.8px;line-height:1.5;color:#6b4a12}
.flag b{color:var(--amber)}
/* opções */
.opts{margin-top:14px;display:flex;flex-wrap:wrap;gap:8px}
.opt{
border:1px solid var(--line);background:var(--panel);border-radius:var(--r);
padding:9px 14px;font-size:13.2px;font-weight:500;color:var(--ink);
transition:.16s;text-align:left;
}
.opt:hover{border-color:var(--blue);background:#f5f9ff;transform:translateY(-1px)}
.opt small{display:block;font-size:11.5px;font-weight:400;color:var(--ink-3);margin-top:2px}
.opt.wide{width:100%}
.typing{display:flex;gap:4px;padding:6px 0}
.typing span{width:5px;height:5px;border-radius:50%;background:var(--ink-3);animation:blip 1.1s infinite}
.typing span:nth-child(2){animation-delay:.16s}
.typing span:nth-child(3){animation-delay:.32s}
@keyframes blip{0%,60%,100%{opacity:.25}30%{opacity:1}}
/* composer */
.composer{border-top:1px solid var(--line);padding:14px 26px 18px;background:var(--canvas)}
.composer textarea{
width:100%;min-height:88px;resize:none;padding:13px 15px;font-family:inherit;font-size:13.5px;line-height:1.55;
border:1px solid var(--line);border-radius:var(--r);background:var(--panel);color:var(--ink);
}
.composer textarea::placeholder{color:#9aa8b8}
.composer textarea:focus{border-color:var(--blue);outline:none}
.composer-row{display:flex;align-items:center;gap:10px;margin-top:10px}
.btn{
background:var(--ink);color:#fff;padding:9px 18px;border-radius:var(--r);
font-size:13.2px;font-weight:600;transition:.16s;
}
.btn:hover{background:#1c3d5e}
.btn:disabled{opacity:.35;cursor:not-allowed}
.btn.ghost{background:none;color:var(--ink-2);border:1px solid var(--line)}
.btn.ghost:hover{background:var(--panel);color:var(--ink)}
.hint{font-size:11.8px;color:var(--ink-3);margin-left:auto}
/* ---------- coluna direita: documento ---------- */
.right{display:flex;flex-direction:column;background:var(--panel);overflow:hidden}
.tabs{display:flex;border-bottom:1px solid var(--line);padding:0 26px;background:var(--panel);flex:none}
.tab{
padding:15px 0;margin-right:26px;font-size:12.5px;font-weight:600;color:var(--ink-3);
border-bottom:2px solid transparent;margin-bottom:-1px;letter-spacing:.01em;
}
.tab.on{color:var(--ink);border-bottom-color:var(--ink)}
.tab .n{font-family:'JetBrains Mono',monospace;font-size:10.5px;background:var(--line-2);color:var(--ink-2);padding:1px 5px;border-radius:2px;margin-left:6px}
.doc-wrap{flex:1;overflow-y:auto;background:#f1f3f6;padding:22px}
.doc-wrap::-webkit-scrollbar{width:9px}
.doc-wrap::-webkit-scrollbar-thumb{background:#c3cad4;border-radius:9px;border:3px solid #f1f3f6}
.doc{
background:var(--paper);border:1px solid var(--line);
padding:34px 38px 40px;max-width:660px;margin:0 auto;
font-family:'Newsreader',Georgia,serif;
}
.doc-head{border-bottom:2px solid var(--ink);padding-bottom:14px;margin-bottom:20px}
.doc-kicker{font-family:'Inter Tight',sans-serif;font-size:10.5px;font-weight:700;letter-spacing:.13em;text-transform:uppercase;color:var(--teal);margin-bottom:7px}
.doc h1{font-size:25px;font-weight:600;line-height:1.2;letter-spacing:-.01em}
.doc h1 .ph{color:#c0c8d2;font-style:italic;font-weight:400}
.doc h2{
font-family:'Inter Tight',sans-serif;font-size:11px;font-weight:700;letter-spacing:.11em;
text-transform:uppercase;color:var(--ink-3);margin:26px 0 11px;
padding-bottom:5px;border-bottom:1px solid var(--line-2);
}
.doc p{font-size:15px;line-height:1.62;margin-bottom:10px;color:#1c3category}
.doc p{color:#1b3049}
/* campos */
.fields{display:grid;grid-template-columns:auto 1fr;gap:0}
.fields dt{
font-family:'Inter Tight',sans-serif;font-size:11.5px;font-weight:600;color:var(--ink-3);
padding:7px 20px 7px 0;border-bottom:1px solid var(--line-2);white-space:nowrap;
}
.fields dd{font-size:14.5px;padding:7px 0;border-bottom:1px solid var(--line-2)}
.fields dt:last-of-type,.fields dd:last-of-type{border-bottom:none}
.val{transition:.3s}
.val.empty{
display:inline-block;font-family:'Inter Tight',sans-serif;font-size:11.5px;
color:var(--amber);background:var(--amber-bg);border:1px dashed #dcc08a;
padding:1px 8px;border-radius:2px;font-style:normal;
}
.val.fresh{animation:pop .5s ease both}
@keyframes pop{0%{background:#fff3d4}100%{background:transparent}}
.mono{font-family:'JetBrains Mono',monospace;font-size:13px;font-weight:500}
/* tabela de entregáveis */
table.ent{width:100%;border-collapse:collapse;font-family:'Inter Tight',sans-serif;margin-top:4px}
table.ent th{
font-size:10px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:var(--ink-3);
text-align:left;padding:0 8px 7px 0;border-bottom:1px solid var(--ink);
}
table.ent th.n,table.ent td.n{text-align:right;padding-right:0}
table.ent td{font-size:12.8px;padding:9px 8px 9px 0;border-bottom:1px solid var(--line-2);vertical-align:top}
table.ent td.n{font-family:'JetBrains Mono',monospace;font-weight:500}
table.ent tr.total td{border-bottom:none;border-top:2px solid var(--ink);font-weight:700;padding-top:9px;font-size:13.2px}
table.ent .nm{font-weight:600;color:var(--ink);display:block;line-height:1.35}
table.ent .meta{font-size:11px;color:var(--ink-3);display:block;margin-top:2px}
.tag{
display:inline-block;font-family:'JetBrains Mono',monospace;font-size:10px;font-weight:700;
padding:2px 6px;border-radius:2px;background:#e8f4f7;color:var(--teal);
}
/* verificação */
.check{margin-top:8px;border:1px solid var(--line);background:#fff}
.check-h{
font-family:'Inter Tight',sans-serif;font-size:10.5px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;
padding:10px 14px;border-bottom:1px solid var(--line-2);color:var(--ink-2);display:flex;align-items:center;gap:8px;
}
.check-h .score{margin-left:auto;font-family:'JetBrains Mono',monospace;color:var(--green)}
.check ul{list-style:none;padding:8px 14px 12px}
.check li{
font-family:'Inter Tight',sans-serif;font-size:12.5px;padding:5px 0 5px 22px;position:relative;color:var(--ink-2);line-height:1.45;
}
.check li::before{position:absolute;left:0;top:5px;font-family:'JetBrains Mono',monospace;font-size:12px}
.check li.ok::before{content:'✓';color:var(--green)}
.check li.warn::before{content:'!';color:var(--amber);font-weight:700;left:4px}
.check li.warn{color:#6b4a12}
/* empty state doc */
.doc-empty{
max-width:660px;margin:0 auto;padding:70px 40px;text-align:center;
border:1px dashed #c3cad4;background:#f7f8fa;
}
.doc-empty h3{font-family:'Newsreader',serif;font-size:21px;font-weight:500;color:var(--ink-2);margin-bottom:9px}
.doc-empty p{font-size:13.5px;color:var(--ink-3);max-width:380px;margin:0 auto;line-height:1.6}
/* painel base */
.kb{padding:22px 26px;overflow-y:auto;flex:1;background:#f1f3f6}
.kb-sec{margin-bottom:26px}
.kb-sec>h3{
font-size:10.5px;font-weight:700;letter-spacing:.11em;text-transform:uppercase;color:var(--ink-3);
margin-bottom:11px;padding-bottom:6px;border-bottom:1px solid var(--line);
}
.rec{
background:#fff;border:1px solid var(--line);padding:12px 14px;margin-bottom:8px;
display:flex;gap:13px;align-items:flex-start;transition:.18s;
}
.rec.hit{border-color:var(--teal);box-shadow:0 0 0 2px #d9eef3}
.rec .rid{font-family:'JetBrains Mono',monospace;font-size:11.5px;font-weight:700;color:var(--ink);flex:none;width:44px}
.rec .rbody{flex:1;min-width:0}
.rec .rt{font-size:13.2px;font-weight:600;line-height:1.35}
.rec .rm{font-size:11.5px;color:var(--ink-3);margin-top:3px;display:flex;gap:9px;flex-wrap:wrap}
.rec .rm span{white-space:nowrap}
.kb-note{font-size:12.5px;color:var(--ink-3);line-height:1.55;padding:10px 12px;background:#fff;border-left:2px solid var(--line)}
@media(max-width:1000px){
body{overflow:auto}
.app{height:auto}
.main{grid-template-columns:1fr}
.left{border-right:none;border-bottom:1px solid var(--line);height:auto}
.stream{max-height:none}
.doc{padding:26px 22px 30px}
.doc-wrap{padding:16px}
}
</style>
</head>
<body>
<div class="app">
<header class="topbar">
<div class="logo">
<svg width="19" height="21" viewBox="0 0 20 22" aria-hidden="true">
<path d="M10 1l8 4.6v9.2L10 20.4 2 14.8V5.6z" fill="#3fb6d0"/>
<path d="M10 5.6l4.3 2.5v4.9L10 15.5l-4.3-2.5V8.1z" fill="#0f2338"/>
</svg>
Redator de Demandas
</div>
<div class="sep"></div>
<div class="ctx">Contrato <b>Pregão 29/2025</b> · SES-MG · Rede Mineira de Dados em Saúde</div>
<div class="right">
<div class="pill" id="pill">Rascunho</div>
<button class="btn-reset" onclick="location.reload()">Recomeçar</button>
</div>
</header>
<div class="main">
<!-- ========== ESQUERDA ========== -->
<section class="left">
<div class="left-head">
<div class="eyebrow">Entrevista</div>
<div class="step" id="stepLabel">passo 0 de 6</div>
</div>
<div class="stream" id="stream"></div>
<div class="composer" id="composer">
<textarea id="input" placeholder="Cole aqui a demanda como ela chegou — trecho de ata, e-mail, mensagem de Teams. Não precisa organizar."></textarea>
<div class="composer-row">
<button class="btn" id="send">Analisar demanda</button>
<button class="btn ghost" id="example">Usar um exemplo</button>
<span class="hint">Nada é enviado — protótipo local</span>
</div>
</div>
</section>
<!-- ========== DIREITA ========== -->
<section class="right">
<div class="tabs">
<button class="tab on" data-tab="doc">Demanda em construção</button>
<button class="tab" data-tab="kb">Base consultada <span class="n" id="kbCount">0</span></button>
</div>
<div class="doc-wrap" id="docWrap">
<div class="doc-empty" id="docEmpty">
<h3>A demanda se escreve enquanto vocês conversam</h3>
<p>Cole o texto bruto ao lado. Cada resposta preenche um campo aqui — enquadramento, entregáveis, USTs e critérios de aceite.</p>
</div>
<div class="doc" id="doc" style="display:none"></div>
</div>
<div class="kb" id="kb" style="display:none"></div>
</section>
</div>
</div>
<script>
/* ============================================================
BASE DE CONHECIMENTO SIMULADA
============================================================ */
const BASE_ENTREGAS = [
{id:'SD25', t:'Índice de Capacidade de Integração Municipal', item:'I-05', ust:320, dur:'6 sem', status:'Entregue', tags:['dashboard','score','regulação','municípios']},
{id:'SD24', t:'Portal RMDS — prototipagem e nota técnica', item:'I-02', ust:240, dur:'6 sem', status:'Pausado', tags:['portal','adesão','municípios']},
{id:'SD22', t:'Ingestão SUS-Fácil — conector e camada bronze', item:'I-02', ust:400, dur:'10 sem', status:'Entregue', tags:['ingestão','regulação','conector','fila']},
{id:'SD19', t:'Automação de giro de leito CORE × FHEMIG', item:'I-04', ust:520, dur:'10 sem', status:'Em execução', tags:['leitos','automação','regulação']},
{id:'SD17', t:'Busca ativa Outubro Rosa — coorte de mamografia', item:'I-05', ust:480, dur:'9 sem', status:'Entregue', tags:['coorte','fila','algoritmo','painel']},
{id:'SD12', t:'Motor de regras vacinais — MG App Vacina em Dia', item:'I-05', ust:440, dur:'9 sem', status:'Entregue', tags:['motor','regras','algoritmo']}
];
const BASE_PLATAFORMA = [
{id:'CAP-01', t:'Camada de ingestão e conectores', d:'Conectores para SUS-Fácil, Vivver, Sisreg e e-SUS já homologados. Fonte nova entra como I-02.'},
{id:'CAP-04', t:'Curadoria semântica e de-para', d:'Harmonização de nomenclatura entre bases não padronizadas. Disponível como serviço.'},
{id:'CAP-07', t:'Produtos analíticos e painéis', d:'Publicação de dashboards na plataforma com controle de acesso por perfil. Enquadra em I-05.'},
{id:'CAP-09', t:'Camada de fila e tempo de espera', d:'Ainda não construída. Depende de ingestão contínua do SUS-Fácil (SD22) e do CORE.'}
];
const REGRAS = {
timebox:{ 'I-02':{c:40}, 'I-03':{c:40}, 'I-04':{c:80}, 'I-05':{c:80} },
nomes:{
'I-02':'Desenvolvimento e implantação de plataforma',
'I-03':'Capacitação e consultoria pós-implantação',
'I-04':'Manutenção, integração e automação',
'I-05':'Serviços técnicos de inteligência e produtos analíticos'
}
};
const DEMANDA_EXEMPLO =
`Pessoal, na reunião de ontem a SUBRAS trouxe de novo a questão da fila de cirurgias eletivas. Hoje ninguém sabe dizer quantas pessoas estão esperando, há quanto tempo, e em qual município elas estão paradas. O secretário pediu uma visão do estado inteiro.
O Guilherme falou que os dados do SUS-Fácil já estão entrando na plataforma desde a integração do mês passado, então em tese dá pra montar alguma coisa. Precisamos definir o que conta como "fila", porque tem solicitação em busca de leito, tem autorizada sem agendamento, tem paciente que já foi operado e não deu baixa.
Também precisa cruzar com o Opera Mais pra saber quais municípios recebem incentivo e têm fila grande. A ideia é ter um painel que o pessoal da regulação abra e consiga priorizar. Prazo: querem alguma coisa até o fim do próximo mês.`;
/* ============================================================
ESTADO
============================================================ */
let step = 0;
const sd = {
titulo:null, projeto:'RMDS — Produto Analítico de Regulação',
problema:null, item:null, entregaveis:[], artefatos:null, gerada:false
};
const kbHits = new Set();
const stream = document.getElementById('stream');
const composer = document.getElementById('composer');
const input = document.getElementById('input');
const doc = document.getElementById('doc');
const docEmpty = document.getElementById('docEmpty');
/* ============================================================
HELPERS DE UI
============================================================ */
function msg(who, html, cls=''){
const d = document.createElement('div');
d.className = 'msg ' + cls;
d.innerHTML = `<div class="who"><i></i>${who}</div><div class="bubble">${html}</div>`;
stream.appendChild(d);
stream.scrollTop = stream.scrollHeight;
return d;
}
function think(cb, ms=750){
const d = document.createElement('div');
d.className='msg';
d.innerHTML = `<div class="who"><i></i>Redator</div><div class="bubble"><div class="typing"><span></span><span></span><span></span></div></div>`;
stream.appendChild(d);
stream.scrollTop = stream.scrollHeight;
setTimeout(()=>{ d.remove(); cb(); }, ms);
}
function sourcesBlock(list){
return `<div class="sources"><div class="sources-h">Consultei na base</div>` +
list.map(s=>`<div class="src"><div class="id">${s.id}</div><div class="txt">${s.txt}</div></div>`).join('') +
`</div>`;
}
function options(list){
const wrap = document.createElement('div');
wrap.className = 'opts';
list.forEach(o=>{
const b = document.createElement('button');
b.className = 'opt' + (o.wide?' wide':'');
b.innerHTML = `${o.label}${o.sub?`<small>${o.sub}</small>`:''}`;
b.onclick = ()=>{
wrap.remove();
msg('Você', o.echo || o.label, 'user');
o.go();
};
wrap.appendChild(b);
});
stream.lastElementChild.querySelector('.bubble').appendChild(wrap);
stream.scrollTop = stream.scrollHeight;
}
function setStep(n){
step = n;
document.getElementById('stepLabel').textContent = `passo ${n} de 6`;
}
function hit(id){ kbHits.add(id); document.getElementById('kbCount').textContent = kbHits.size; renderKB(); }
/* ============================================================
FLUXO DA ENTREVISTA
============================================================ */
document.getElementById('example').onclick = ()=>{ input.value = DEMANDA_EXEMPLO; input.focus(); };
document.getElementById('send').onclick = ()=>{
const v = input.value.trim();
if(!v) return;
composer.style.display='none';
msg('Você', v.split('\n\n').map(p=>`<p>${p}</p>`).join(''), 'user');
setStep(1);
q1();
};
/* ---- 1. leitura e primeira devolutiva ---- */
function q1(){
think(()=>{
['SD22','SD17','CAP-09'].forEach(hit);
msg('Redator', `
<p>Li a demanda. O que eu entendi: a SES-MG não consegue enxergar a fila de cirurgias eletivas do estado — nem volume, nem tempo de espera, nem distribuição por município — e quer um painel que permita priorizar.</p>
<p>Antes de enquadrar, tem uma coisa que preciso confirmar, porque muda o item e o custo.</p>
${sourcesBlock([
{id:'SD22', txt:'<b>Ingestão SUS-Fácil</b> — conector e camada bronze entregues. A fonte já está na plataforma, então não é preciso construir ingestão de novo.'},
{id:'CAP-09', txt:'<b>Camada de fila e tempo de espera</b> — ainda não construída na plataforma. É a peça que falta.'},
{id:'SD17', txt:'<b>Busca ativa Outubro Rosa</b> — precedente de coorte + painel, enquadrado em I-05, 480 UST.'}
])}
<p style="margin-top:14px">A definição de fila — o que conta como paciente esperando — já existe em algum lugar?</p>
`, 'bot');
options([
{label:'Não existe, precisa ser construída com a SUBRAS', sub:'Vai exigir descoberta negocial e validação de regra', wide:true, echo:'Não existe. Vamos ter que definir com eles.', go:()=>q2('nova')},
{label:'Existe, mas está em documento antigo e desatualizado', sub:'Descoberta mais curta, só validação', wide:true, echo:'Existe algo antigo, mas precisa revalidar.', go:()=>q2('revisar')}
]);
}, 1100);
}
/* ---- 2. natureza do trabalho → item ---- */
function q2(origem){
sd.problema = origem;
setStep(2);
think(()=>{
msg('Redator', `
<p>Certo. Então a demanda tem duas naturezas possíveis e eu preciso saber onde está o peso do trabalho — é isso que define o item do contrato.</p>
<p class="quiet">O painel em si é inteligência. Mas se for preciso trazer fonte nova ou construir conector, aí entra plataforma, e vira entregável separado.</p>
`, 'bot');
options([
{label:'Só o painel e a regra de fila', sub:'Os dados do SUS-Fácil já estão na plataforma', wide:true, echo:'Só painel e regra. Os dados já estão lá.', go:()=>q3('I-05')},
{label:'Painel + trazer o CORE Saúde como fonte nova', sub:'Cruza dois itens — I-02 para a ingestão, I-05 para o painel', wide:true, echo:'Vamos precisar do CORE também.', go:()=>q3('duplo')}
]);
});
}
/* ---- 3. enquadramento ---- */
function q3(modo){
sd.item = modo === 'duplo' ? 'I-05 + I-02' : 'I-05';
setStep(3);
think(()=>{
let extra = '';
if(modo === 'duplo'){
extra = `<div class="flag"><b>Atenção.</b> Trazer o CORE agora abre uma frente de I-02 com dependência externa — a SUBASS informou que a extração do CORE está suspensa por ajustes no sistema novo. Recomendo abrir como SD separada e não travar o painel.</div>`;
hit('SD22');
}
hit('CAP-07');
msg('Redator', `
<p>Enquadrei em <b>I-05</b>. Painel gerencial, algoritmo de classificação e priorização — é a regra do manual: dashboard, índice ou análise vai em I-05, não em I-04.</p>
${extra}
${sourcesBlock([
{id:'CAP-07', txt:'<b>Produtos analíticos e painéis</b> — publicação com controle de acesso por perfil já é capacidade da plataforma. Não precisa ser construída.'},
{id:'SD17', txt:'Mesmo enquadramento e escopo comparável: coorte + painel de priorização em I-05.'}
])}
<p style="margin-top:14px">Agora os entregáveis. Quantas frentes de trabalho você enxerga até o painel no ar?</p>
`, 'bot');
options([
{label:'Três — descobrir a regra, desenhar o painel, construir', sub:'Padrão da maioria das SDs de produto analítico', wide:true, echo:'Três: descoberta, design e construção.', go:()=>q4('tres')},
{label:'Quatro — com arquitetura do modelo de dados separada', sub:'Se a modelagem da fila for complexa o bastante', wide:true, echo:'Quatro, incluindo arquitetura.', go:()=>q4('quatro')}
]);
});
}
/* ---- 4. dimensionamento ---- */
function q4(qtd){
setStep(4);
const base = [
{n:'Discovery da regra de fila cirúrgica', tipo:'Descoberta', item:'I-05', sem:3, tb:40},
{n:'Especificação do painel de priorização', tipo:'Design', item:'I-05', sem:2, tb:40}
];
if(qtd === 'quatro') base.push({n:'Modelo de dados da fila e tempo de espera', tipo:'Arquitetura', item:'I-05', sem:2, tb:40});
base.push({n:'Painel de fila cirúrgica publicado', tipo:'Construção', item:'I-05', sem:3, tb:80});
sd.entregaveis = base;
renderDoc();
think(()=>{
const total = base.reduce((a,e)=>a+e.sem*e.tb,0);
msg('Redator', `
<p>Montei o quadro ao lado. Dimensionei a descoberta em 3 semanas porque definir fila com a SUBRAS envolve área finalística e casos de borda — solicitação em busca de leito, autorizada sem agendamento, alta sem baixa. Isso não fecha em duas.</p>
<p>Dá <b class="mono">${total} UST</b>. A construção pesa porque em I-05 o time-box é de 80h por semana.</p>
<div class="flag"><b>Prazo.</b> A demanda pede o painel até o fim do próximo mês. Esse desenho leva ${base.reduce((a,e)=>a+e.sem,0)} semanas encadeadas. Ou a expectativa é recalibrada, ou o discovery cai para 2 semanas com escopo de fila reduzido a cirurgias eletivas apenas.</div>
<p style="margin-top:14px">Última coisa: o que já existe hoje que sirva de comprovação?</p>
`, 'bot');
options([
{label:'Ata da reunião com a SUBRAS e o pedido do secretário', sub:'Lastro para a descoberta', wide:true, echo:'Tenho a ata e o registro do pedido.', go:()=>q5(true)},
{label:'Nada formalizado ainda — a demanda veio em reunião', sub:'A SD abre a demanda antes da execução', wide:true, echo:'Nada ainda. É demanda nova.', go:()=>q5(false)}
]);
}, 900);
}
/* ---- 5. fechamento ---- */
function q5(temLastro){
sd.artefatos = temLastro;
setStep(5);
think(()=>{
msg('Redator', `
<p>${temLastro
? 'Bom. A ata entra como artefato comprobatório do entregável de descoberta e ancora a data de abertura.'
: 'Então essa SD abre demanda futura, não formaliza execução passada. Isso simplifica: os artefatos são projetados, não recuperados.'}</p>
<p>Tenho o suficiente para escrever. Vou gerar a SD com quadro de entregáveis, enquadramento no TR, artefatos e critérios de aceite por entregável.</p>
`, 'bot');
options([
{label:'Gerar a demanda', wide:true, echo:'Pode gerar.', go:()=>gerar()}
]);
});
}
function gerar(){
setStep(6);
document.getElementById('pill').textContent = 'Pronta para revisão';
document.getElementById('pill').style.background = 'rgba(63,182,208,.2)';
document.getElementById('pill').style.color = '#7fd8ea';
sd.gerada = true;
sd.titulo = 'Painel de Fila Cirúrgica Eletiva';
renderDoc();
think(()=>{
msg('Redator', `
<p>Pronta. Rodei o checklist de validação e passou em tudo, com uma ressalva registrada ao lado sobre o prazo.</p>
<p class="quiet">O que você faz agora: revisa o contexto — é a parte que precisa da sua leitura política —, confirma o título literal do item contra o TR e exporta.</p>
<div class="flag"><b>Nesta versão do protótipo</b> a exportação para .docx e o envio ao fluxo de aprovação ainda não estão ligados.</div>
`, 'bot');
const wrap = document.createElement('div');
wrap.className='opts';
wrap.innerHTML = `<button class="opt" disabled style="opacity:.45;cursor:not-allowed">Exportar .docx</button><button class="opt" disabled style="opacity:.45;cursor:not-allowed">Enviar para aprovação</button>`;
stream.lastElementChild.querySelector('.bubble').appendChild(wrap);
stream.scrollTop = stream.scrollHeight;
}, 1200);
}
/* ============================================================
RENDER DO DOCUMENTO
============================================================ */
function ph(txt){ return `<span class="val empty">${txt}</span>`; }
function renderDoc(){
docEmpty.style.display='none';
doc.style.display='block';
const total = sd.entregaveis.reduce((a,e)=>a+e.sem*e.tb,0);
const semanas = sd.entregaveis.reduce((a,e)=>a+e.sem,0);
let linhas = sd.entregaveis.map((e,i)=>`
<tr>
<td class="n" style="width:22px;color:#8b9aab">${i+1}</td>
<td><span class="nm">${e.n}</span><span class="meta">${e.tipo} · time-box ${e.tb}h/semana</span></td>
<td style="width:46px"><span class="tag">${e.item}</span></td>
<td class="n" style="width:52px">${e.sem} sem</td>
<td class="n" style="width:56px">${e.sem*e.tb}</td>
</tr>`).join('');
if(sd.entregaveis.length){
linhas += `<tr class="total"><td></td><td>Total</td><td></td><td class="n">${semanas} sem</td><td class="n">${total}</td></tr>`;
}
doc.innerHTML = `
<div class="doc-head">
<div class="doc-kicker">Solicitação de Demanda · rascunho automático</div>
<h1>${sd.titulo ? sd.titulo : '<span class="ph">Título a definir</span>'}</h1>
</div>
<dl class="fields">
<dt>Projeto</dt><dd>${sd.projeto}</dd>
<dt>Contratante</dt><dd>Secretaria de Estado de Saúde de Minas Gerais</dd>
<dt>Contrato</dt><dd class="mono">Pregão Eletrônico nº 29/2025 — Processo 1321127</dd>
<dt>Item do TR</dt><dd>${sd.item ? `<b>${sd.item}</b> — ${REGRAS.nomes['I-05']}` : ph('aguardando enquadramento')}</dd>
<dt>Total de UST</dt><dd>${total ? `<span class="mono">${total}</span>` : ph('aguardando entregáveis')}</dd>
<dt>Entregáveis</dt><dd>${sd.entregaveis.length || ph('—')}</dd>
</dl>
<h2>Contexto e escopo</h2>
${sd.gerada ? `
<p>A SES-MG não dispõe hoje de visão consolidada da fila de cirurgias eletivas. Não é possível responder quantos pacientes aguardam, há quanto tempo e em que municípios a espera se concentra — o que impede priorização e enfraquece a gestão do Opera Mais, programa que financia produção cirúrgica cuja demanda represada permanece invisível.</p>
<p>Os dados do SUS-Fácil já foram integrados à plataforma. O que falta é a camada de interpretação: definir o que constitui fila, tratar os estados intermediários da solicitação e transformar registros de regulação em tempo de espera mensurável por município e procedimento.</p>
<p>Esta demanda entrega a regra de fila validada com a área finalística e o painel que a torna operável pela regulação estadual.</p>
` : `<p style="color:#93a1b2;font-style:italic">O contexto é redigido ao final da entrevista, a partir das respostas.</p>`}
<h2>Quadro de entregáveis</h2>
${sd.entregaveis.length ? `
<table class="ent">
<thead><tr><th></th><th>Entregável</th><th>Item</th><th class="n">Prazo</th><th class="n">UST</th></tr></thead>
<tbody>${linhas}</tbody>
</table>
` : `<p style="color:#93a1b2;font-style:italic">Os entregáveis aparecem aqui conforme a conversa avança.</p>`}
${sd.gerada ? `
<h2>Enquadramento no Termo de Referência</h2>
<dl class="fields">
<dt>Item</dt><dd><b>I-05</b> — ${REGRAS.nomes['I-05']}</dd>
<dt>Aderência</dt><dd style="font-size:14px;line-height:1.55">
Concepção do algoritmo de classificação de fila e tempo de espera a partir dos registros de regulação;
desenvolvimento do modelo de priorização por município e procedimento;
construção e publicação do painel gerencial com controle de acesso por perfil.
</dd>
</dl>
<h2>Verificação automática</h2>
<div class="check">
<div class="check-h">Checklist do manual <span class="score">10 de 11</span></div>
<ul>
<li class="ok">Cada entregável tem item definido</li>
<li class="ok">Dashboard e algoritmo enquadrados em I-05</li>
<li class="ok">Nenhum entregável abaixo de 2 nem acima de 4 semanas</li>
<li class="ok">Time-box de construção correto para I-05 (80h)</li>
<li class="ok">UST de cada linha confere com time-box × semanas</li>
<li class="ok">Soma das USTs bate com o cabeçalho</li>
<li class="ok">Entregáveis encadeados, sem sobreposição de período</li>
<li class="ok">Produto com nome único ao longo do documento</li>
<li class="ok">Todo critério de aceite é verificável por terceiro</li>
<li class="ok">Nenhum campo confessa irregularidade</li>
<li class="warn">Prazo solicitado pelo demandante é menor que a duração desenhada — precisa de repactuação antes do envio</li>
</ul>
</div>
` : ''}
`;
doc.querySelectorAll('.val').forEach(v=>v.classList.add('fresh'));
}
/* ============================================================
PAINEL DA BASE
============================================================ */
function renderKB(){
const el = document.getElementById('kb');
el.innerHTML = `
<div class="kb-sec">
<h3>Entregas anteriores · ${BASE_ENTREGAS.length} registros</h3>
${BASE_ENTREGAS.map(r=>`
<div class="rec ${kbHits.has(r.id)?'hit':''}">
<div class="rid">${r.id}</div>
<div class="rbody">
<div class="rt">${r.t}</div>
<div class="rm"><span>${r.item}</span><span>${r.ust} UST</span><span>${r.dur}</span><span>${r.status}</span></div>
</div>
</div>`).join('')}
</div>
<div class="kb-sec">
<h3>Capacidades da plataforma</h3>
${BASE_PLATAFORMA.map(r=>`
<div class="rec ${kbHits.has(r.id)?'hit':''}">
<div class="rid">${r.id}</div>
<div class="rbody">
<div class="rt">${r.t}</div>
<div class="rm" style="display:block;line-height:1.45">${r.d}</div>
</div>
</div>`).join('')}
</div>
<div class="kb-sec">
<h3>Regras aplicadas</h3>
<div class="kb-note">Time-box de 40h/semana em Descoberta, Design e Arquitetura. Construção a 40h em I-02 e I-03, e a 80h em I-04 e I-05. Cada entregável entre 2 e 4 semanas. Entregáveis de itens diferentes nunca somam na mesma linha.</div>
</div>
`;
}
renderKB();
/* tabs */
document.querySelectorAll('.tab').forEach(t=>{
t.onclick = ()=>{
document.querySelectorAll('.tab').forEach(x=>x.classList.remove('on'));
t.classList.add('on');
const isDoc = t.dataset.tab === 'doc';
document.getElementById('docWrap').style.display = isDoc ? 'block' : 'none';
document.getElementById('kb').style.display = isDoc ? 'none' : 'block';
};
});
/* abertura */
msg('Redator', `
<p>Me conta a demanda do jeito que ela chegou. Pode ser trecho de ata, e-mail encaminhado, print de conversa — não precisa organizar nada.</p>
<p class="quiet">Eu leio, comparo com o que já entregamos e com o que a plataforma já faz, e vou te perguntando só o que ainda falta para fechar a SD.</p>
`, 'bot');
</script>
</body>
</html>