/* global React */ /* =========================================================== VOLTLAB · Calculation Modules — Corrente, Queda, Dimensionamento Logic adapted from technical reference (NBR 5410). =========================================================== */ const { useState, useMemo, useEffect } = React; const D = window.VL_DATA; // ===================================================== // HOOKS E LÓGICA DE EXPORTAÇÃO (JSON/CSV) // ===================================================== const usePersist = (key, initial) => { if (window.useLocalStorage) return window.useLocalStorage(key, initial); return React.useState(initial); // fallback }; const buildReport = (moduleName, inputs, result) => ({ modulo: moduleName, norma: "NBR 5410 / ANEEL 414", geradoEm: new Date().toLocaleString("pt-BR"), entradas: inputs, resultado: result, }); const downloadBlob = (content, filename, mime) => { const blob = new Blob([content], { type: mime }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = filename; a.click(); URL.revokeObjectURL(url); }; window.VL_exportReport = (format) => { if (!window._VL_lastExport) return; const report = window._VL_lastExport(); if (!report) return; if (format === "csv") { const rows = [ ["Campo", "Valor"], ["Módulo", report.modulo], ["Gerado em", report.geradoEm], ["---ENTRADAS---", ""], ...Object.entries(report.entradas).map(([k, v]) => [k, v]), ["---RESULTADO---", ""], ...Object.entries(report.resultado).map(([k, v]) => [k, typeof v === "object" ? JSON.stringify(v) : v]) ]; const csv = rows.map(r => r.map(c => `"${String(c).replace(/"/g,'""')}"`).join(";")).join("\n"); downloadBlob(csv, `voltlab-${report.modulo}.csv`, "text/csv;charset=utf-8"); } else { downloadBlob(JSON.stringify(report, null, 2), `voltlab-${report.modulo}.json`, "application/json"); } }; /* Small shared atoms ---------------------------------------- */ const VL_Icon = ({ name, size = 18 }) => { const props = { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round" }; const paths = { bolt: , wave: , cable: <>, pipe: <>, refresh: <>, download: <>, plus: <>, check: , warn: <>, info: <>, }; return {paths[name]}; }; const ResultBig = ({ label, value, unit, status }) => (
{label}
{value} {unit}
); const MetricCell = ({ label, value, unit, status, big }) => (
{label}
{value} {unit && {unit}}
); const StatusBanner = ({ kind, children }) => (
{children}
); const MemoryStep = ({ n, title, children, isFormula }) => (
{String(n).padStart(2, "0")} {title}
{children}
); const FieldBlock = ({ label, hint, children }) => (
{children}
); const NumberInput = ({ value, onChange, suffix, step, min, max, placeholder }) => (
onChange(e.target.value)} step={step ?? "0.01"} min={min} max={max} placeholder={placeholder ?? "0"} /> {suffix && {suffix}}
); const SelectInput = ({ value, onChange, children, options }) => { if (options && window.Dropdown_VL) { return ; } return (
); }; const Seg = ({ value, onChange, options }) => (
{options.map(o => ( ))}
); /* =========================================================== 1. CORRENTE ELÉTRICA =========================================================== */ const Corrente = () => { const [potencia, setPotencia] = useState("50"); const [unidade, setUnidade] = useState("kW"); const [tensao, setTensao] = useState("380"); const [sistema, setSistema] = useState("trifasico"); const [decimais, setDecimais] = useState("3"); const calc = useMemo(() => { const P = parseFloat(potencia); const V = parseFloat(tensao); const d = parseInt(decimais); if (!P || !V || P <= 0 || V <= 0) return null; let Pw; if (unidade === "W") Pw = P; else if (unidade === "kW") Pw = P * 1000; else Pw = P * 735.5; // CV let I, formula, sub; if (sistema === "trifasico") { I = Pw / (V * Math.sqrt(3)); formula = "I = P / (V · √3)"; sub = `I = ${Pw.toLocaleString("pt-BR")} / (${V} · 1,732)`; } else { I = Pw / V; formula = "I = P / V"; sub = `I = ${Pw.toLocaleString("pt-BR")} / ${V}`; } return { I, Pw, formula, sub, d }; }, [potencia, unidade, tensao, sistema, decimais]); return (
ENTRADA · POTÊNCIA E TENSÃO

Dados do circuito

AUTO-CÁLCULO
{window.VL_ProjetoSync && { const t=s.get("tensao"); if(t!=null) setTensao(String(t)); const sis=s.get("sistema"); if(sis) setSistema(sis); }} push={()=>({ tensao: parseFloat(tensao), sistema, corrente: calc? +calc.I.toFixed(2): undefined, potencia: calc? +(calc.Pw/1000).toFixed(3): undefined })} />}
Sistema{sistema === "trifasico" ? "3F" : sistema === "bifasico" ? "2F" : "1F"} · {tensao || 0} V
Potência ativa{(calc?.Pw ?? 0).toLocaleString("pt-BR", {maximumFractionDigits: 1})} W
Conversão{unidade} → W
); }; /* =========================================================== 2. QUEDA DE TENSÃO =========================================================== */ const QuedaTensao = () => { const [dist, setDist] = usePersist("vl_queda_dist", "265"); const [tensao, setTensao] = usePersist("vl_queda_tensao", "380"); const [corrente, setCorrente] = usePersist("vl_queda_corrente", "16.05"); const [secao, setSecao] = usePersist("vl_queda_secao", "10"); const [sistema, setSistema] = usePersist("vl_queda_sistema", "trifasico"); const [material, setMaterial] = usePersist("vl_queda_material", "0.0175"); const calc = useMemo(() => { const L = parseFloat(dist), V = parseFloat(tensao), I = parseFloat(corrente), S = parseFloat(secao); const rho = parseFloat(material); if (!L || !V || !I || !S || L<=0 || V<=0 || I<=0 || S<=0) return null; let dV, formula, sub; if (sistema === "monofasico") { dV = (2 * rho * L * I) / S; formula = "ΔV = 2 · ρ · L · I / S"; sub = `ΔV = 2 · ${rho} · ${L} · ${I} / ${S}`; } else { dV = (Math.sqrt(3) * rho * L * I) / S; formula = "ΔV = √3 · ρ · L · I / S"; sub = `ΔV = 1,732 · ${rho} · ${L} · ${I} / ${S}`; } const pct = (dV / V) * 100; const limite = 4; const status = pct > limite ? "danger" : pct > 3 ? "warn" : "ok"; return { dV, pct, limite, status, formula, sub, rho, L, V, I, S }; }, [dist, tensao, corrente, secao, sistema, material]); useEffect(() => { window._VL_lastExport = () => calc ? buildReport("queda-tensao", { distancia_m: dist, tensao_V: tensao, corrente_A: corrente, secao_mm2: secao, sistema, material: material === "0.0175" ? "Cobre" : "Alumínio", }, { queda_V: calc.dV.toFixed(3), queda_pct: calc.pct.toFixed(2), status: calc.status, limite_pct: calc.limite, }) : null; return () => { window._VL_lastExport = null; }; }, [calc]); return (
ENTRADA · PARÂMETROS DO CIRCUITO

Configuração do trecho

AUTO-CÁLCULO
{window.VL_ProjetoSync && { const c=s.get("corrente"); if(c!=null) setCorrente(String(c)); const t=s.get("tensao"); if(t!=null) setTensao(String(t)); const sec=s.get("secao"); if(sec!=null) setSecao(String(sec)); const sis=s.get("sistema"); if(sis&&sis!=="bifasico") setSistema(sis); }} push={()=>({ tensao: parseFloat(tensao), corrente: parseFloat(corrente), secao: parseFloat(secao), sistema })} />}
({ value: String(s), label: `${s} mm²` })) }/>
L · I{(parseFloat(dist)||0) * (parseFloat(corrente)||0)}
Sistema{sistema === "trifasico" ? "Trifásico" : "Monofásico"}
Limite NBR≤ 4%
); }; /* =========================================================== 3. DIMENSIONAMENTO DE CABOS =========================================================== */ const Dimensionamento = () => { const [tipo, setTipo] = useState("3CA"); const [tempAmb, setTempAmb] = useState("40"); const [dist, setDist] = useState("300"); const [tensao, setTensao] = useState("380"); const [potencia, setPotencia] = useState("150"); const [quedaMax, setQuedaMax] = useState("5"); const [fp, setFp] = useState("0.8"); const [rend, setRend] = useState("90"); const [isolacao, setIsolacao] = useState("PVC"); const [agrup, setAgrup] = useState("1"); // adjust defaults when type changes useEffect(() => { if (tipo === "CC") { setPotencia("20"); setTensao("24"); setDist("100"); } else if (tipo === "3CA") { setPotencia("150"); setTensao("380"); setDist("300"); } else { setPotencia("7000"); setTensao("220"); setDist("300"); } }, [tipo]); const result = useMemo(() => { const L = parseFloat(dist), V = parseFloat(tensao), P = parseFloat(potencia); const qMax = parseFloat(quedaMax) / 100; if (!L || !V || !P || !qMax) return null; const rho20 = 0.0175; const alpha = 0.00393; const rho = rho20 * (1 + alpha * (parseFloat(tempAmb) - 20)); let corrente, table = []; let secaoQueda = null, secaoAmpacidade = null; const sections = D.commercialSections; const fpN = parseFloat(fp), rendN = parseFloat(rend) / 100; if (tipo === "CC") { const Rl = (V * V) / P; corrente = P / V; sections.forEach(s => { const Rcond = rho * L / s; const Rcirc = Rl + 2 * Rcond; const Vf = (Rl / Rcirc) * V; const qV = V - Vf; const qPct = (qV / V) * 100; table.push({ s, Rcond, qV, qPct }); if (!secaoQueda && (qPct / 100) <= qMax) secaoQueda = s; }); } else if (tipo === "3CA") { const Pkw = P * 0.736; // CV → kW corrente = (Pkw * 1000) / (Math.sqrt(3) * V * rendN * fpN); const sinFi = Math.sin(Math.acos(fpN)); const X = 0.08 * L / 1000; sections.forEach(s => { const Rcond = rho * L / s; const qV = Math.sqrt(3) * corrente * (Rcond * fpN + X * sinFi); const qPct = (qV / V) * 100; table.push({ s, Rcond, qV, qPct }); if (!secaoQueda && (qPct / 100) <= qMax) secaoQueda = s; }); const fctT = D.getTempFactor(parseFloat(tempAmb), isolacao); const fctA = D.getAgrupFactor(parseInt(agrup)); const iBase = corrente / (fctT * fctA); secaoAmpacidade = D.getSectionByAmpacity(iBase, isolacao, true); } else { // 1CA corrente = P / V; sections.forEach(s => { const Rcond = rho * L / s; const qV = 2 * Rcond * corrente; const qPct = (qV / V) * 100; table.push({ s, Rcond, qV, qPct }); if (!secaoQueda && (qPct / 100) <= qMax) secaoQueda = s; }); const fctT = D.getTempFactor(parseFloat(tempAmb), isolacao); const fctA = D.getAgrupFactor(parseInt(agrup)); const iBase = corrente / (fctT * fctA); secaoAmpacidade = D.getSectionByAmpacity(iBase, isolacao, false); } const secaoFinal = Math.max(secaoQueda || 300, secaoAmpacidade || 0); const final = table.find(t => t.s === secaoFinal); return { corrente, table, secaoQueda, secaoAmpacidade, secaoFinal, final, rho }; }, [tipo, tempAmb, dist, tensao, potencia, quedaMax, fp, rend, isolacao, agrup]); const isCA = tipo !== "CC"; const potLabel = tipo === "3CA" ? "Potência (CV)" : "Potência (W)"; return (
ENTRADA · DIMENSIONAMENTO NBR 5410

Parâmetros do sistema e da instalação

AUTO-CÁLCULO
{window.VL_ProjetoSync && { const t=s.get("tensao"); if(t!=null) setTensao(String(t)); }} push={()=>({ tensao: parseFloat(tensao), corrente: result? +result.corrente.toFixed(2): undefined, secao: result? result.secaoFinal: undefined })} />}
{isCA && ( <>
)}
{result && (
TABELA COMPLETA DE SEÇÕES

Iteração por bitola comercial

ρ corrigida = {result.rho.toFixed(6)} Ω·mm²/m
SEÇÃO (mm²) R CONDUTOR (Ω) QUEDA (V) QUEDA (%) STATUS
{result.table.map(r => { const isSel = r.s === result.secaoFinal; const ok = r.qPct <= parseFloat(quedaMax); return (
{r.s} {r.Rcond.toFixed(4)} {r.qV.toFixed(3)} V {r.qPct.toFixed(2)} % {ok ? "Atende" : "Não atende"}
); })}
)}
); }; /* =========================================================== 4. BANCO DE CAPACITORES — correção de fator de potência =========================================================== */ // Bancos comerciais (kVAr) — degraus padrão const CAP_STEPS_BT = [2.5, 5, 7.5, 10, 12.5, 15, 20, 25, 30, 37.5, 50, 60, 75, 100]; const CAP_STEPS_MT = [50, 75, 100, 150, 200, 300, 400, 500, 750, 1000]; const Capacitor = () => { const [pAtiva, setPAtiva] = useState("100"); const [fpAtual, setFpAtual] = useState("0.75"); const [fpDesejado, setFpDesejado] = useState("0.92"); const [tensao, setTensao] = useState("380"); const [sistema, setSistema] = useState("trifasico"); const [freq, setFreq] = useState("60"); const [classe, setClasse] = useState("BT"); // BT ou MT const calc = useMemo(() => { const P = parseFloat(pAtiva); const fp1 = parseFloat(fpAtual); const fp2 = parseFloat(fpDesejado); const V = parseFloat(tensao); const f = parseFloat(freq); if (!P || !fp1 || !fp2 || !V || P <= 0 || fp1 <= 0 || fp1 > 1 || fp2 <= 0 || fp2 > 1) return null; if (fp2 <= fp1) return { error: "FP desejado deve ser maior que o atual" }; const phi1 = Math.acos(fp1); const phi2 = Math.acos(fp2); const tan1 = Math.tan(phi1); const tan2 = Math.tan(phi2); // Qc em kVAr const Qc = P * (tan1 - tan2); // Antes const S1 = P / fp1; const Q1 = P * tan1; const I1 = sistema === "trifasico" ? (S1 * 1000) / (Math.sqrt(3) * V) : (S1 * 1000) / V; // Depois const S2 = P / fp2; const Q2 = P * tan2; const I2 = sistema === "trifasico" ? (S2 * 1000) / (Math.sqrt(3) * V) : (S2 * 1000) / V; // Redução de corrente / perdas const redCorrente = ((I1 - I2) / I1) * 100; const redPerdas = (1 - Math.pow(I2 / I1, 2)) * 100; // Capacitância equivalente (μF) — banco delta trifásico const omega = 2 * Math.PI * f; let cap_uF; if (sistema === "trifasico") { // Delta: C = Qc / (3 · ω · V²) — kVAr e V em SI cap_uF = (Qc * 1000) / (3 * omega * V * V) * 1e6; } else { cap_uF = (Qc * 1000) / (omega * V * V) * 1e6; } // Bancos comerciais — proposta de degraus const steps = classe === "MT" ? CAP_STEPS_MT : CAP_STEPS_BT; // pick smallest single step >= Qc, OR closest combination of equal steps const singleStep = steps.find(s => s >= Qc) ?? steps[steps.length - 1]; // best combination: try 1-6 steps of each commercial value let best = { step: singleStep, n: 1, total: singleStep, sobra: singleStep - Qc }; steps.forEach(s => { for (let n = 1; n <= 8; n++) { const total = s * n; if (total >= Qc) { const sobra = total - Qc; if (sobra < best.sobra || (sobra === best.sobra && n < best.n)) { best = { step: s, n, total, sobra }; } break; } } }); // Multa ANEEL — referência 414/2010 (FP < 0,92) const limite = 0.92; const multa = fp1 < limite; return { P, fp1, fp2, V, f, Qc, S1, S2, Q1, Q2, I1, I2, redCorrente, redPerdas, cap_uF, singleStep, best, multa, limite, tan1, tan2, phi1, phi2, }; }, [pAtiva, fpAtual, fpDesejado, tensao, sistema, freq, classe]); const fpClass = (fp) => fp >= 0.92 ? "ok" : fp >= 0.85 ? "warn" : "danger"; return (
ENTRADA · CORREÇÃO DE FATOR DE POTÊNCIA

Dados da carga e meta de FP

ANEEL 414
{/* Quick comparison */}
ANTES
{parseFloat(fpAtual || 0).toFixed(2)}
DEPOIS
{parseFloat(fpDesejado || 0).toFixed(2)}
{calc && !calc.error && (
ANÁLISE DETALHADA · ANTES × DEPOIS

Impacto da correção no sistema

Frequência: {calc.f} Hz
GRANDEZA SÍMBOLO ANTES DEPOIS VARIAÇÃO
MEMÓRIA DE CÁLCULO
φ₁ = arccos({calc.fp1.toFixed(2)}) = {(calc.phi1*180/Math.PI).toFixed(2)}°
φ₂ = arccos({calc.fp2.toFixed(2)}) = {(calc.phi2*180/Math.PI).toFixed(2)}°
Qc = P · (tan φ₁ − tan φ₂) Qc = {calc.P} · ({calc.tan1.toFixed(4)} − {calc.tan2.toFixed(4)}) Qc = {calc.Qc.toFixed(2)} kVAr {sistema === "trifasico" ? "C = Qc / (3 · ω · V²)" : "C = Qc / (ω · V²)"} {" → "} {calc.cap_uF.toFixed(2)} μF
)}
); }; const FPDial = ({ fp, status }) => { const R = 38, C = 2 * Math.PI * R; const pct = Math.max(0, Math.min(1, fp)); const dash = pct * C; const color = status === "ok" ? "var(--ok)" : status === "warn" ? "var(--warn)" : "var(--danger)"; return ( ); }; const FpRow = ({ label, sym, unit, before, after, delta, good }) => (
{label} {sym} {before} {unit} {after} {unit} {delta}
); /* =========================================================== 5. TRANSFORMADORES =========================================================== */ const Transformador = () => { const [potencia, setPotencia] = usePersist("vl_tr_pot", "500"); const [vPrim, setVPrim] = usePersist("vl_tr_vp", "13.8"); const [vSec, setVSec] = usePersist("vl_tr_vs", "380"); const calc = useMemo(() => { const S = parseFloat(potencia), Vp = parseFloat(vPrim), Vs = parseFloat(vSec); if (!S || !Vp || !Vs) return null; // I = S / (√3 * V) const ip = (S * 1000) / (Math.sqrt(3) * Vp * 1000); const is = (S * 1000) / (Math.sqrt(3) * Vs); // Dimensionamento rápido de proteção (1.25x In para Trafo) const djSec = is * 1.25; return { S, Vp, Vs, ip, is, djSec }; }, [potencia, vPrim, vSec]); useEffect(() => { window._VL_lastExport = () => calc ? buildReport("transformador", { potencia_kVA: potencia, tensao_primaria_kV: vPrim, tensao_secundaria_V: vSec }, { corrente_primaria_A: calc.ip.toFixed(2), corrente_secundaria_A: calc.is.toFixed(2) }) : null; return () => { window._VL_lastExport = null; }; }, [calc]); return (
DADOS DE ENTRADA · NBR 5440

Especificações do Transformador

); }; /* =========================================================== 6. CURTO-CIRCUITO (MÉTODO INFINITO / IMPEDÂNCIA) =========================================================== */ const CurtoCircuito = () => { const [potencia, setPotencia] = usePersist("vl_cc_pot", "500"); const [z, setZ] = usePersist("vl_cc_z", "4.5"); const [tensao, setTensao] = usePersist("vl_cc_v", "380"); const calc = useMemo(() => { const S = parseFloat(potencia), imp = parseFloat(z), V = parseFloat(tensao); if (!S || !imp || !V) return null; // Corrente Nominal const In = (S * 1000) / (Math.sqrt(3) * V); // Corrente de Curto-Circuito Simétrica (Icc = In / Z%) const Icc = In / (imp / 100); const Icc_kA = Icc / 1000; // Corrente Assimétrica de pico (Aprox 2.5x Icc simétrica em BT) const Icc_pico = Icc_kA * 2.5; return { In, Icc, Icc_kA, Icc_pico, imp }; }, [potencia, z, tensao]); useEffect(() => { window._VL_lastExport = () => calc ? buildReport("curto-circuito", { potencia_trafo_kVA: potencia, impedancia_pct: z, tensao_V: tensao }, { icc_simetrica_kA: calc.Icc_kA.toFixed(2), icc_pico_kA: calc.Icc_pico.toFixed(2) }) : null; return () => { window._VL_lastExport = null; }; }, [calc]); return (
DADOS DE ENTRADA · IEC 60909

Parâmetros da Fonte (Transformador)

); }; /* =========================================================== 7. SPDA (SISTEMA DE PROTEÇÃO CONTRA DESCARGAS ATMOSFÉRICAS) Baseado na NBR 5419-3:2015 (Tabelas 2 e 4) =========================================================== */ const SPDA = () => { const [classe, setClasse] = usePersist("vl_spda_classe", "III"); const [altura, setAltura] = usePersist("vl_spda_h", "15"); const [comprimento, setComprimento] = usePersist("vl_spda_c", "30"); const [largura, setLargura] = usePersist("vl_spda_l", "20"); const calc = useMemo(() => { const h = parseFloat(altura), c = parseFloat(comprimento), l = parseFloat(largura); if (!h || !c || !l) return null; // Parâmetros NBR 5419-3:2015 (Tabelas 2 e 4) // r: Raio da Esfera Rolante (m) | malha: Tamanho da Malha (m) | descida: Espaçamento médio (m) const param = { "I": { r: 20, malha: 5, descida: 10 }, "II": { r: 30, malha: 10, descida: 10 }, "III": { r: 45, malha: 15, descida: 15 }, "IV": { r: 60, malha: 20, descida: 20 } }[classe]; const perimetro = 2 * (c + l); // NBR 5419-3: O número mínimo de condutores de descida é 2, posicionados no perímetro let nDescidas = Math.ceil(perimetro / param.descida); if (nDescidas < 2) nDescidas = 2; // Aplicação do Método do Ângulo de Proteção (Franklin) // Só é válido se a altura da edificação for menor ou igual ao raio da esfera da classe escolhida. const franklinAplicavel = h <= param.r; return { h, c, l, perimetro, param, nDescidas, franklinAplicavel }; }, [classe, altura, comprimento, largura]); useEffect(() => { window._VL_lastExport = () => calc ? buildReport("spda", { classe_protecao: classe, altura_m: altura, comprimento_m: comprimento, largura_m: largura }, { raio_esfera_rolante_m: calc.param.r, malha_captores_m: `${calc.param.malha} x ${calc.param.malha}`, descidas_minimas: calc.nDescidas, espacamento_descidas_m: calc.param.descida }) : null; return () => { window._VL_lastExport = null; }; }, [calc, classe, altura, comprimento, largura]); return (
DADOS DE ENTRADA · NBR 5419-3:2015

Geometria e Nível de Proteção

ATUALIZADO
); }; /* Export to window for app.jsx to use */ Object.assign(window, { VL_Corrente: Corrente, VL_QuedaTensao: QuedaTensao, VL_Dimensionamento: Dimensionamento, VL_Capacitor: Capacitor, VL_Transformador: Transformador, VL_CurtoCircuito: CurtoCircuito, // ADICIONE ESTA LINHA: VL_SPDA: SPDA, VL_Icon, VL_ResultBig: ResultBig, VL_MetricCell: MetricCell, // ... resto do código });