// SGAB v2 — Calibração RBC e Rastreada
const { useState, useMemo } = React;
function Calibracao({ user, toast, onNav }) {
const [db, setDb] = useState(() => SGAB.getDB());
const [view, setView] = useState('lista'); // lista | nova | detalhe | cert
const [sel, setSel] = useState(null);
const [tipo, setTipo] = useState('RBC');
const [form, setForm] = useState(null);
const { fmt } = SGAB;
function reload() { setDb(SGAB.getDB()); }
// ── NOVA CALIBRAÇÃO: INIT FORM ────────────────────────────────
function novaCalib(t) {
setTipo(t);
setForm({
tipo:t, data:fmt.today(), local:'In Situ', tecnico:user.nome,
clienteId:'', equipamentoId:'', osId:'',
cond:{ temp:'22', umid:'55', press:'1013', densAr:'1.193' },
padroes:[{ tag:'PAD-001', cert:'', val:'', rastr:'IPT/INMETRO', classe:'F1', cap:'', res:'', incert:'', k:'2' }],
pontos:[
{nom:'',l1:'',l2:'',l3:''},
{nom:'',l1:'',l2:'',l3:''},
{nom:'',l1:'',l2:'',l3:''},
{nom:'',l1:'',l2:'',l3:''},
{nom:'',l1:'',l2:'',l3:''},
],
excent:[
{pos:'A — Centro', carga:'',leit:''},
{pos:'B — Frente', carga:'',leit:''},
{pos:'C — Trás', carga:'',leit:''},
{pos:'D — Esquerda', carga:'',leit:''},
{pos:'E — Direita', carga:'',leit:''},
{pos:'F — Centro', carga:'',leit:''},
],
conclusao:'CONFORME', obs:''
});
setView('nova');
}
// ── SALVAR CALIBRAÇÃO ─────────────────────────────────────────
function salvarCalib() {
if (!form.clienteId) { toast('Selecione o cliente.','error'); return; }
if (!form.equipamentoId) { toast('Selecione o equipamento.','error'); return; }
const ndb = SGAB.getDB();
const id = SGAB.nxtId(ndb,'calib');
const ano2 = String(new Date().getFullYear()).slice(2);
const mes2 = String(new Date().getMonth()+1).padStart(2,'0');
const dia2 = String(new Date().getDate()).padStart(2,'0');
const eq = ndb.equipamentos.find(e=>e.id===+form.equipamentoId);
const cli = ndb.clientes.find(c=>c.id===+form.clienteId);
const numero = tipo==='RBC' ? `CCR_${String(id+429).padStart(3,'0')}` : `CC_${Date.now()}`;
const sufixo = `${dia2}.${mes2}.${ano2}_${(cli?.fantasia||'').replace(/\s/g,'').slice(0,8)}`;
// Auto-calculate pontos
const pontos = form.pontos.filter(p=>p.nom!=='').map(p=>{
if (!p.l1||!p.l2||!p.l3) return {...p,med:0,err:0,corr:0,tol:50,incU:0,ok:true};
const r = SGAB.calcPonto(p.l1,p.l2,p.l3,p.nom);
const pad = form.padroes[0];
const div = eq?.divE?.replace(/[^\d.]/g,'')||'10';
const iu = SGAB.calcIncerteza(div,pad?.incert||'0.01',r.rep);
const tol = calcTolerance(+p.nom, eq?.cap?.replace(/[^\d.]/g,'')||1000, eq?.classe||'III');
return {...p, med:r.med, err:r.err, corr:r.corr, tol, incU:+iu.U.toFixed(2), ok:Math.abs(r.err)<=tol};
});
const conclusao = pontos.every(p=>p.ok) ? 'CONFORME' : 'NÃO CONFORME';
const newCalib = { id, tipo, numero, sufixo, clienteId:+form.clienteId, equipamentoId:+form.equipamentoId,
osId:form.osId?+form.osId:null, data:form.data, local:form.local, tecnico:form.tecnico,
cond:form.cond, padroes:form.padroes, pontos, excent:form.excent, conclusao, obs:form.obs };
ndb.calibracoes.push(newCalib);
// Atualiza ultima calibração do equip
const eIdx = ndb.equipamentos.findIndex(e=>e.id===+form.equipamentoId);
if(eIdx>=0){
ndb.equipamentos[eIdx].ultimaCalib = form.data;
const proxDate = new Date(form.data); proxDate.setFullYear(proxDate.getFullYear()+1);
ndb.equipamentos[eIdx].proxCalib = proxDate.toISOString().slice(0,10);
}
SGAB.saveDB(ndb); reload();
setSel(newCalib); setView('detalhe');
toast(`Calibração ${numero} salva com sucesso!`,'success');
}
function calcTolerance(valor, cap, classe) {
const c = +cap;
if (classe==='II') return valor <= c*0.5 ? c*0.001 : valor <= c*0.8 ? c*0.002 : c*0.003;
if (classe==='IIII') return valor <= c*0.5 ? c*0.02 : valor <= c*0.8 ? c*0.03 : c*0.05;
// Classe III (padrão)
return valor <= c*0.5 ? c*0.01 : valor <= c*0.8 ? c*0.02 : c*0.03;
}
// ── CERTIFICADO PDF ───────────────────────────────────────────
function gerarCertificado(calib) {
const cli = db.clientes.find(c=>c.id===calib.clienteId);
const eq = db.equipamentos.find(e=>e.id===calib.equipamentoId);
const config = db.config.empresa;
const isRBC = calib.tipo==='RBC';
const tituloCert = isRBC ? 'CERTIFICADO DE CALIBRAÇÃO RBC' : 'CERTIFICADO DE CONFORMIDADE — CALIBRAÇÃO RASTREADA';
const html = `
${calib.numero}
01 — IDENTIFICAÇÃO DO INSTRUMENTO
| Fabricante | ${eq?.fab||'—'} | Modelo | ${eq?.modelo||'—'} |
| N° de Série | ${eq?.serie||'—'} | TAG / Patrimônio | ${eq?.tag||'—'} |
| Capacidade | ${eq?.cap||'—'} | Divisão (d=e) | ${eq?.divD||'—'} |
| Classe de Exatidão | ${eq?.classe||'—'} | Portaria | ${eq?.portaria||'—'} |
| Localização | ${eq?.local||'—'} — ${cli?.razaoSocial||'—'}, ${cli?.cidade||'—'}/${cli?.uf||'—'} |
|---|
02 — CONDIÇÕES AMBIENTAIS
| Temperatura (°C) | ${calib.cond?.temp||'—'} | Umidade Relativa (%) | ${calib.cond?.umid||'—'} |
| Pressão Atm. (hPa) | ${calib.cond?.press||'—'} | Densidade do Ar (kg/m³) | ${calib.cond?.densAr||'—'} |
|---|
03 — PADRÕES DE TRABALHO UTILIZADOS
| TAG | N° Certificado | Validade | Rastreabilidade | Classe | Capacidade | Resolução | Incerteza | k |
${(calib.padroes||[]).map(p=>`| ${p.tag} | ${p.cert} | ${fmt.date(p.val)} | ${p.rastr} | ${p.classe} | ${p.cap} | ${p.res} | ${p.incert} g | ${p.k} |
`).join('')}
04 — RESULTADOS DE CALIBRAÇÃO — ERROS E CORREÇÕES
| Carga Nominal (g) | Leitura 1 | Leitura 2 | Leitura 3 | Média | Erro (g) | Correção (g) | Tolerância (g) | Incerteza U (g) | Resultado |
${(calib.pontos||[]).map(p=>`| ${p.nom} | ${p.l1} | ${p.l2} | ${p.l3} | ${p.med} | ${p.err} | ${p.corr} | ±${p.tol} | ${p.incU} | ${p.ok?'CONFORME':'NÃO CONFORME'} |
`).join('')}
05 — ENSAIO DE EXCENTRICIDADE
| Posição | Carga de Ensaio (g) | Leitura (g) | Diferença (g) |
${(calib.excent||[]).map((e,i,arr)=>{const ref=arr[0]?.leit||0;const diff=+e.leit-+ref;return`| ${e.pos} | ${e.carga} | ${e.leit} | ${diff>=0?'+':''}${diff} |
`}).join('')}
${calib.conclusao==='CONFORME'?'✓ CONFORME':'✗ NÃO CONFORME'} — ${calib.conclusao==='CONFORME'?'O instrumento atende aos requisitos metrológicos aplicáveis.':'O instrumento não atende aos requisitos metrológicos. Recomenda-se ajuste/reparo.'}
${calib.obs?`OBSERVAÇÕES
${calib.obs}
`:''}
Técnico Responsável
${calib.tecnico||user.nome}
Levi Balanças
Responsável pelo Cliente
${cli?.contato||'—'}
${cli?.razaoSocial||'—'}
${isRBC?`
Responsável Técnico RBC
Acreditação ${config.acred}
Data: ${fmt.date(calib.data)}
`:''}