/* global React */ /* =========================================================== VOLTLAB · MÓDULO DE ILUMINAÇÃO Dimensionamento pelo Método dos Lúmens (NBR ISO/CIE 8995-1) Cálculo de circuitos de iluminação conforme NBR 5410. Funcionalidades: ─ Cálculo de fluxo luminoso total e número de luminárias ─ Fator de utilização (FU) por tipo de ambiente e teto ─ Fator de depreciação (Fd) configurável ─ Dimensionamento do circuito elétrico (cabo, disjuntor) ─ Memória de cálculo passo-a-passo ─ Gerenciador de múltiplos ambientes por projeto ─ Exportação JSON/CSV =========================================================== */ const { useState, useMemo, useEffect, useCallback, useRef } = React; const usePersist = (key, initial) => { if (window.useLocalStorage) return window.useLocalStorage(key, initial); return React.useState(initial); }; /* =========================================================== DADOS TÉCNICOS =========================================================== */ const IL_DATA = { /* Iluminâncias recomendadas NBR ISO/CIE 8995-1 (lux) */ ambientes: [ { id: "escritorio", label: "Escritório / Sala de trabalho", E: 500, rho_teto: 0.70, rho_parede: 0.50, k_ref: 1.25 }, { id: "sala_reuniao", label: "Sala de reunião / Conferência", E: 500, rho_teto: 0.70, rho_parede: 0.50, k_ref: 1.25 }, { id: "corredor", label: "Corredor / Circulação", E: 100, rho_teto: 0.70, rho_parede: 0.50, k_ref: 0.80 }, { id: "banheiro", label: "Banheiro / Vestiário", E: 200, rho_teto: 0.70, rho_parede: 0.50, k_ref: 0.80 }, { id: "cozinha", label: "Cozinha / Copa", E: 300, rho_teto: 0.70, rho_parede: 0.50, k_ref: 1.00 }, { id: "sala_estar", label: "Sala de estar / Recepção", E: 200, rho_teto: 0.70, rho_parede: 0.50, k_ref: 1.00 }, { id: "dormitorio", label: "Dormitório / Quarto", E: 150, rho_teto: 0.70, rho_parede: 0.50, k_ref: 0.80 }, { id: "garagem", label: "Garagem / Estacionamento", E: 75, rho_teto: 0.50, rho_parede: 0.30, k_ref: 0.75 }, { id: "deposito", label: "Depósito / Almoxarifado", E: 100, rho_teto: 0.50, rho_parede: 0.30, k_ref: 0.80 }, { id: "fabrica_grossa", label: "Fabricação — trabalho grosso", E: 200, rho_teto: 0.50, rho_parede: 0.30, k_ref: 0.90 }, { id: "fabrica_media", label: "Fabricação — trabalho médio", E: 500, rho_teto: 0.50, rho_parede: 0.30, k_ref: 1.00 }, { id: "fabrica_fina", label: "Fabricação — trabalho fino", E: 750, rho_teto: 0.50, rho_parede: 0.30, k_ref: 1.25 }, { id: "laboratorio", label: "Laboratório / Sala técnica", E: 750, rho_teto: 0.70, rho_parede: 0.50, k_ref: 1.25 }, { id: "sala_aula", label: "Sala de aula / Treinamento", E: 500, rho_teto: 0.70, rho_parede: 0.50, k_ref: 1.25 }, { id: "biblioteca", label: "Biblioteca / Arquivo", E: 500, rho_teto: 0.70, rho_parede: 0.50, k_ref: 1.25 }, { id: "area_externa", label: "Área externa / Pátio", E: 50, rho_teto: 0.10, rho_parede: 0.10, k_ref: 0.60 }, { id: "personalizado", label: "Personalizado…", E: 500, rho_teto: 0.70, rho_parede: 0.50, k_ref: 1.00 }, ], /* Lâmpadas / luminárias típicas */ lampadas: [ { id: "led_tubular_18w", label: "LED Tubular 18W / 120cm", P: 18, Fl: 2000, fp: 0.92 }, { id: "led_tubular_36w", label: "LED Tubular 36W / 120cm", P: 36, Fl: 4000, fp: 0.92 }, { id: "led_painel_25w", label: "LED Painel 25W / 60×60cm", P: 25, Fl: 2500, fp: 0.95 }, { id: "led_painel_36w", label: "LED Painel 36W / 60×60cm", P: 36, Fl: 3600, fp: 0.95 }, { id: "led_downlight_9w", label: "LED Downlight 9W", P: 9, Fl: 800, fp: 0.90 }, { id: "led_downlight_15w", label: "LED Downlight 15W", P: 15, Fl: 1500, fp: 0.92 }, { id: "led_highbay_100w", label: "LED HighBay 100W (industrial)", P: 100, Fl: 14000, fp: 0.95 }, { id: "led_highbay_150w", label: "LED HighBay 150W (industrial)", P: 150, Fl: 21000, fp: 0.95 }, { id: "led_highbay_200w", label: "LED HighBay 200W (industrial)", P: 200, Fl: 28000, fp: 0.95 }, { id: "led_spot_7w", label: "LED Spot 7W (GU10)", P: 7, Fl: 600, fp: 0.85 }, { id: "led_spot_12w", label: "LED Spot 12W (GU10)", P: 12, Fl: 1050, fp: 0.88 }, { id: "fluoresc_hf_32w", label: "Fluoresc. HF 2×32W", P: 64, Fl: 5200, fp: 0.92 }, { id: "fluoresc_hf_54w", label: "Fluoresc. HF 2×54W", P: 108, Fl: 9800, fp: 0.92 }, { id: "sodio_250w", label: "Vapor Sódio 250W", P: 275, Fl: 27500, fp: 0.85 }, { id: "sodio_400w", label: "Vapor Sódio 400W", P: 440, Fl: 48000, fp: 0.85 }, { id: "personalizado_lam", label: "Personalizado…", P: 36, Fl: 3600, fp: 0.92 }, ], /* Fator de depreciação */ fd_opcoes: [ { value: "0.80", label: "0,80 — Manutenção regular (recomendado)" }, { value: "0.75", label: "0,75 — Manutenção periódica" }, { value: "0.70", label: "0,70 — Manutenção esporádica" }, { value: "0.65", label: "0,65 — Ambiente agressivo / poeirento" }, { value: "0.60", label: "0,60 — Sem manutenção programada" }, ], /* Refletâncias de teto */ rho_teto_opcoes: [ { value: "0.80", label: "0,80 — Branco puro" }, { value: "0.70", label: "0,70 — Branco / creme" }, { value: "0.50", label: "0,50 — Cinza claro / bege" }, { value: "0.30", label: "0,30 — Cinza médio / cor" }, { value: "0.10", label: "0,10 — Escuro / concreto aparente" }, ], /* Seções comerciais de cabos */ secoes: [1.5, 2.5, 4, 6, 10, 16, 25], /* Ampacidade tabela B1 embutido (A) para cabo PVC bifásico */ ampacity_pvc_2: { 1.5: 17, 2.5: 22, 4: 30, 6: 38, 10: 52, 16: 71, 25: 93 }, /* Disjuntores comerciais (A) */ disjuntores: [6, 10, 16, 20, 25, 32, 40, 50, 63], }; /* =========================================================== LÓGICA DE CÁLCULO =========================================================== */ function calcularKr(comprimento, largura, hm) { // Índice do local (k) = (C × L) / (hm × (C + L)) const area = comprimento * largura; const perimetro = comprimento + largura; if (!hm || !perimetro) return 0; return area / (hm * perimetro); } function calcularFU(kr, rho_teto, rho_parede, tipoLuminaria) { // Aproximação por polinômio calibrado por tipo de teto e luminária // Baseado em tabelas de FU típicas (luminária direta embutida) const rho_t = parseFloat(rho_teto); const rho_p = parseFloat(rho_parede); // Peso das refletâncias const rflex = (rho_t * 0.55 + rho_p * 0.30 + 0.15 * 0.20); // FU base pela curva do índice k let fu_base; const k = Math.min(Math.max(kr, 0.5), 5); if (k < 1) fu_base = 0.28 + 0.18 * (k - 0.5) / 0.5; else if (k < 2) fu_base = 0.46 + 0.14 * (k - 1); else if (k < 3) fu_base = 0.60 + 0.07 * (k - 2); else if (k < 4) fu_base = 0.67 + 0.04 * (k - 3); else fu_base = 0.71 + 0.02 * (k - 4); // Ajuste por refletância const fu = fu_base * (0.65 + 0.35 * rflex * 1.8); return Math.min(Math.max(fu, 0.20), 0.85); } function calcularAmbiente(params) { const { comprimento, largura, alturaTotal, alturaPE, E_alvo, fd, rho_teto, rho_parede, lamp_P, lamp_Fl, lamp_fp, tensao, sistema, } = params; const C = parseFloat(comprimento); const L = parseFloat(largura); const hTotal = parseFloat(alturaTotal); const hPE = parseFloat(alturaPE); // altura do plano de trabalho const E = parseFloat(E_alvo); const Fd = parseFloat(fd); const Fl = parseFloat(lamp_Fl); const P_lam = parseFloat(lamp_P); const fp = parseFloat(lamp_fp); const V = parseFloat(tensao); if (!C || !L || !hTotal || !E || !Fd || !Fl || !P_lam) return null; const area = C * L; // hm = altura das luminárias sobre o plano de trabalho const hm = hTotal - hPE; if (hm <= 0) return null; // Índice do local const kr = calcularKr(C, L, hm); // Fator de utilização const fu = calcularFU(kr, rho_teto, rho_parede); // Fluxo total necessário: Φtotal = (E × A) / (FU × Fd) const phi_total = (E * area) / (fu * Fd); // Número de luminárias const n_float = phi_total / Fl; const n_min = Math.ceil(n_float); // Iluminância resultante com n_min luminárias const E_result = (n_min * Fl * fu * Fd) / area; // Potência total instalada const P_total = n_min * P_lam; // W // Densidade de potência (W/m²) const DPI = P_total / area; // ── Dimensionamento elétrico ────────────────────────────── // Corrente de projeto let I_proj; const isMono = sistema === "monofasico"; if (isMono) { I_proj = P_total / (V * fp); } else { I_proj = P_total / (V * Math.sqrt(3) * fp); } // Corrente de dimensionamento = Ib (sem fatores de correção por simplicidade; // o usuário pode usar o módulo de cabos para cálculo rigoroso) const I_dim = I_proj * 1.25; // fator de segurança 125% para circuito de iluminação // Seção mínima por ampacidade let secao_cabo = 1.5; for (const s of IL_DATA.secoes) { const amp = IL_DATA.ampacity_pvc_2[s]; if (amp >= I_dim) { secao_cabo = s; break; } secao_cabo = s; } // Disjuntor mínimo let disj = IL_DATA.disjuntores[IL_DATA.disjuntores.length - 1]; for (const d of IL_DATA.disjuntores) { if (d >= I_proj * 1.0) { disj = d; break; } } // Queda de tensão estimada (sem comprimento aqui — cálculo simplificado) const rho_cu = 0.0175; // Ω·mm²/m return { C, L, area, hm, kr, fu, Fd, phi_total, n_float, n_min, E_result, E_alvo: E, P_total, DPI, I_proj, I_dim, secao_cabo, disj, lamp_P: P_lam, lamp_Fl: Fl, lamp_fp: fp, status_E: E_result >= E * 0.95 ? "ok" : "warn", status_dpi: DPI <= 15 ? "ok" : DPI <= 25 ? "warn" : "danger", }; } /* =========================================================== COMPONENTES ATOMS (seguindo padrão VoltLab) =========================================================== */ const IL_Icon = ({ name, size = 18 }) => { const p = { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round" }; const icons = { bulb: <>, room: <>, plus: <>, trash: <>, check: , warn: <>, info: <>, download: <>, refresh: <>, copy: <>, lux: <>, bolt: , arrow: <>, }; return {icons[name] || null}; }; const IL_ResultBig = ({ label, value, unit, status }) => (
{label}
{value} {unit}
); const IL_MetricCell = ({ label, value, unit, status }) => (
{label}
{value} {unit && {unit}}
); const IL_StatusBanner = ({ kind, children }) => (
{children}
); const IL_MemoryStep = ({ n, title, children, isFormula }) => (
{String(n).padStart(2, "0")} {title}
{children}
); const IL_FieldBlock = ({ label, hint, children }) => (
{children}
); const IL_NumberInput = ({ value, onChange, suffix, step, min }) => (
onChange(e.target.value)} step={step ?? "0.1"} min={min ?? "0"} placeholder="0"/> {suffix && {suffix}}
); const IL_Select = ({ value, onChange, options }) => { if (window.Dropdown_VL) { return ; } return (
); }; const IL_Seg = ({ value, onChange, options }) => (
{options.map(o => ( ))}
); /* =========================================================== VISUALIZADOR DE PLANTA BAIXA (mini canvas) =========================================================== */ const PlantaBaixa = ({ comprimento, largura, n_luminarias }) => { const W = 260, H = 180; const C = parseFloat(comprimento) || 0; const L = parseFloat(largura) || 0; if (!C || !L) return null; const PAD = 24; const scaleX = (W - PAD * 2) / C; const scaleY = (H - PAD * 2) / L; const scale = Math.min(scaleX, scaleY); const rW = C * scale, rH = L * scale; const ox = (W - rW) / 2, oy = (H - rH) / 2; // Grid de luminárias const n = parseInt(n_luminarias) || 0; const cols = Math.max(1, Math.ceil(Math.sqrt(n * C / L))); const rows = Math.ceil(n / cols); const lumPos = []; for (let r = 0; r < rows; r++) { for (let c = 0; c < cols; c++) { if (lumPos.length >= n) break; const lx = ox + (rW / cols) * (c + 0.5); const ly = oy + (rH / rows) * (r + 0.5); lumPos.push([lx, ly]); } } return ( {/* Room outline */} {/* Dimension labels */} {C.toFixed(1)}m {L.toFixed(1)}m {/* Luminárias */} {lumPos.map(([lx, ly], i) => ( {/* luz irradiada */} {[0,45,90,135,180,225,270,315].map(a => { const rad = a * Math.PI / 180; return ; })} ))} {/* Badge */} {n > 0 && ( {n} luminária{n !== 1 ? "s" : ""} )} ); }; /* =========================================================== CARD DE AMBIENTE (na lista de projetos) =========================================================== */ const AmbienteCard = ({ amb, index, isSelected, onSelect, onRemove, result, canRemove = true }) => (
{result && ( )} {canRemove && ( )}
{amb.nome || `Ambiente ${index + 1}`}
{result ? (
{result.n_min} lum {result.E_result.toFixed(0)} lx {result.P_total.toFixed(0)} W
) : (
Preencha os dados
)}
); /* =========================================================== FORMULÁRIO DE UM AMBIENTE =========================================================== */ const AmbienteForm = ({ amb, onChange }) => { const update = (key, val) => onChange({ ...amb, [key]: val }); const ambSel = IL_DATA.ambientes.find(a => a.id === amb.tipoAmbiente); const lampSel = IL_DATA.lampadas.find(l => l.id === amb.tipoLampada); // Sincroniza defaults ao mudar tipo de ambiente const onAmbChange = (id) => { const a = IL_DATA.ambientes.find(x => x.id === id); if (a && id !== "personalizado") { onChange({ ...amb, tipoAmbiente: id, E_alvo: String(a.E), rho_teto: String(a.rho_teto), rho_parede: String(a.rho_parede) }); } else { update("tipoAmbiente", id); } }; // Sincroniza defaults ao mudar lâmpada const onLampChange = (id) => { const l = IL_DATA.lampadas.find(x => x.id === id); if (l && id !== "personalizado_lam") { onChange({ ...amb, tipoLampada: id, lamp_P: String(l.P), lamp_Fl: String(l.Fl), lamp_fp: String(l.fp) }); } else { update("tipoLampada", id); } }; const isPersonalAmb = amb.tipoAmbiente === "personalizado"; const isPersonalLam = amb.tipoLampada === "personalizado_lam"; return (
{/* Nome */}
update("nome", e.target.value)}/>
{/* Tipo de ambiente */} ({ value: a.id, label: a.label }))} /> {/* Iluminância */}
update("E_alvo", v)} suffix="lx" step="25" min="25"/> update("fd", v)} options={IL_DATA.fd_opcoes} />
{/* Dimensões */}
DIMENSÕES DO AMBIENTE
update("comprimento", v)} suffix="m" step="0.1"/> update("largura", v)} suffix="m" step="0.1"/>
update("alturaTotal", v)} suffix="m" step="0.05"/> update("alturaPE", v)} suffix="m" step="0.05"/>
{/* Refletâncias */}
REFLETÂNCIAS DA SUPERFÍCIE
update("rho_teto", v)} options={IL_DATA.rho_teto_opcoes} /> update("rho_parede", v)} options={[ { value: "0.70", label: "0,70 — Branco / creme" }, { value: "0.50", label: "0,50 — Claro / bege" }, { value: "0.30", label: "0,30 — Cinza médio / cor" }, { value: "0.10", label: "0,10 — Escuro" }, ]} />
{/* Luminária */}
LUMINÁRIA / LÂMPADA
({ value: l.id, label: l.label }))} /> {isPersonalLam && (
update("lamp_P", v)} suffix="W" step="1"/> update("lamp_Fl", v)} suffix="lm" step="100"/>
)} {/* Dados elétricos */}
CIRCUITO ELÉTRICO
update("tensao", v)} options={[ { value: "127", label: "127 V (monofásico)" }, { value: "220", label: "220 V (monofásico)" }, { value: "380", label: "380 V (trifásico)" }, ]} /> update("sistema", v)} options={[{ value: "monofasico", label: "Monof." }, { value: "trifasico", label: "Trifás." }]} />
{/* Comprimento do circuito para queda de tensão */} update("comp_circuito", v)} suffix="m" step="1"/>
); }; /* =========================================================== PAINEL DE RESULTADOS =========================================================== */ const ResultPanel = ({ result, amb }) => { if (!result) { return (
Preencha os dados do ambiente para calcular.
); } const { C, L, area, hm, kr, fu, Fd, phi_total, n_float, n_min, E_result, E_alvo, P_total, DPI, I_proj, I_dim, secao_cabo, disj, status_E, status_dpi } = result; // Queda de tensão (com comprimento do circuito se informado) const L_circ = parseFloat(amb.comp_circuito) || 0; let quedaV = null, quedaPct = null, statusQueda = "info"; if (L_circ > 0 && secao_cabo) { const rho = 0.0175; const V = parseFloat(amb.tensao) || 220; quedaV = (2 * rho * L_circ * I_proj) / secao_cabo; quedaPct = (quedaV / V) * 100; statusQueda = quedaPct > 4 ? "danger" : quedaPct > 3 ? "warn" : "ok"; } return (
{/* Resultado principal */}
{status_E === "ok" ? Iluminância de {E_result.toFixed(0)} lx ≥ {E_alvo} lx mínimo (NBR 8995-1) : Verifique os parâmetros — iluminância pode estar abaixo do mínimo } {status_dpi === "ok" ? DPI dentro do limite de eficiência energética (≤ 15 W/m²) : status_dpi === "warn" ? DPI elevado — considere luminárias mais eficientes : DPI crítico — luminárias LED de maior eficácia recomendadas } {/* Visualização da planta baixa */}
LAYOUT ESTIMADO · PLANTA BAIXA
{/* Circuito elétrico */}
DIMENSIONAMENTO DO CIRCUITO · NBR 5410
{quedaV !== null && (
{statusQueda !== "ok" && (
Queda de tensão {quedaPct.toFixed(2)}% — limite NBR 5410 ≤ 4%
)}
)}
{/* Memória de cálculo */}
MEMÓRIA DE CÁLCULO · MÉTODO DOS LÚMENS
A = {C.toFixed(1)} m × {L.toFixed(1)} m = {area.toFixed(2)} m² h_m = h_teto − h_plano = {(parseFloat(amb.alturaTotal)||0).toFixed(2)} − {(parseFloat(amb.alturaPE)||0).toFixed(2)} = {hm.toFixed(2)} m k = (C × L) / (h_m × (C + L)) = ({C.toFixed(1)} × {L.toFixed(1)}) / ({hm.toFixed(2)} × {(C+L).toFixed(1)}) = {kr.toFixed(3)} FU estimado para k = {kr.toFixed(2)}, ρ_teto = {(parseFloat(amb.rho_teto)||0.7).toFixed(2)}, ρ_parede = {(parseFloat(amb.rho_parede)||0.5).toFixed(2)}: {fu.toFixed(3)} Φ_total = (E × A) / (FU × Fd) = ({parseFloat(amb.E_alvo).toFixed(0)} × {area.toFixed(2)}) / ({fu.toFixed(3)} × {Fd.toFixed(2)}) = {phi_total.toFixed(0)} lm N = Φ_total / Φ_luminária = {phi_total.toFixed(0)} / {(parseFloat(amb.lamp_Fl)||0).toFixed(0)} = {n_float.toFixed(2)} → arredondar = {n_min} und. E_real = (N × Φ_lum × FU × Fd) / A = ({n_min} × {(parseFloat(amb.lamp_Fl)||0).toFixed(0)} × {fu.toFixed(3)} × {Fd.toFixed(2)}) / {area.toFixed(2)} = {E_result.toFixed(1)} lx I_b = P_total / (V × FP) = {P_total.toFixed(0)} / ({parseFloat(amb.tensao)||220} × {(parseFloat(amb.lamp_fp)||0.92).toFixed(2)}) = {I_proj.toFixed(2)} A I_dim = 1,25 × I_b = 1,25 × {I_proj.toFixed(2)} = {I_dim.toFixed(2)} A → cabo {secao_cabo} mm² (ampacidade = {IL_DATA.ampacity_pvc_2[secao_cabo]} A)
); }; /* =========================================================== RESUMO DO PROJETO (visão consolidada) =========================================================== */ const ResumoProjeto = ({ ambientes, resultados }) => { const validos = ambientes.filter((_, i) => resultados[i]); if (validos.length === 0) return Adicione e calcule ambientes para ver o resumo.; const totalLuminarias = resultados.filter(Boolean).reduce((s, r) => s + r.n_min, 0); const totalPotencia = resultados.filter(Boolean).reduce((s, r) => s + r.P_total, 0); const totalArea = resultados.filter(Boolean).reduce((s, r) => s + r.area, 0); const dpiMedio = totalPotencia / totalArea; return (
{/* Tabela por ambiente */}
TABELA DE ILUMINAÇÃO — QUADRO RESUMO
{["Ambiente", "Área", "E_alvo", "E_real", "FU", "Fd", "Lum.", "Pot.", "DPI"].map(h => ( ))} {ambientes.map((a, i) => { const r = resultados[i]; if (!r) return null; return ( ); })}
{h}
{a.nome || `Amb. ${i+1}`} {r.area.toFixed(1)} m² {r.E_alvo} lx {r.E_result.toFixed(0)} lx {r.fu.toFixed(2)} {r.Fd.toFixed(2)} {r.n_min} {r.P_total.toFixed(0)} W {r.DPI.toFixed(1)} W/m²
TOTAL {totalArea.toFixed(1)} m² {totalLuminarias} un. {totalPotencia.toFixed(0)} W {dpiMedio.toFixed(1)}
); }; /* =========================================================== ESTADO PADRÃO DE UM AMBIENTE =========================================================== */ const newAmbiente = () => ({ nome: "", tipoAmbiente: "escritorio", E_alvo: "500", fd: "0.80", comprimento: "10", largura: "8", alturaTotal: "2.8", alturaPE: "0.75", rho_teto: "0.70", rho_parede: "0.50", tipoLampada: "led_painel_36w", lamp_P: "36", lamp_Fl: "3600", lamp_fp: "0.95", tensao: "220", sistema: "monofasico", comp_circuito: "", }); /* =========================================================== COMPONENTE PRINCIPAL — Iluminação =========================================================== */ const Iluminacao = () => { const [ambientes, setAmbientes] = usePersist("vl_iluminacao_ambientes", [newAmbiente()]); const [selectedIdx, setSelectedIdx] = usePersist("vl_iluminacao_selected", 0); const [tab, setTab] = useState("calc"); // calc | resumo // Garante índice válido const idx = Math.min(selectedIdx, ambientes.length - 1); const amb = ambientes[idx] || newAmbiente(); // Calcular todos os ambientes const resultados = useMemo(() => ambientes.map(a => { const lamp = IL_DATA.lampadas.find(l => l.id === a.tipoLampada); return calcularAmbiente({ comprimento: a.comprimento, largura: a.largura, alturaTotal: a.alturaTotal, alturaPE: a.alturaPE, E_alvo: a.E_alvo, fd: a.fd, rho_teto: a.rho_teto, rho_parede: a.rho_parede, lamp_P: a.lamp_P || (lamp?.P ?? 36), lamp_Fl: a.lamp_Fl || (lamp?.Fl ?? 3600), lamp_fp: a.lamp_fp || (lamp?.fp ?? 0.92), tensao: a.tensao, sistema: a.sistema, }); }), [ambientes]); const result = resultados[idx]; // Exportação useEffect(() => { window._VL_lastExport = () => { if (!result) return null; return { modulo: "iluminacao", norma: "NBR ISO/CIE 8995-1 / NBR 5410", geradoEm: new Date().toLocaleString("pt-BR"), entradas: { ambiente: amb.tipoAmbiente, nome: amb.nome, comprimento_m: amb.comprimento, largura_m: amb.largura, pe_direito_m: amb.alturaTotal, plano_trabalho_m: amb.alturaPE, iluminancia_lx: amb.E_alvo, fd: amb.fd, rho_teto: amb.rho_teto, rho_parede: amb.rho_parede, lampada: amb.tipoLampada, potencia_lampada_W: amb.lamp_P, fluxo_lampada_lm: amb.lamp_Fl, tensao_V: amb.tensao, sistema: amb.sistema, }, resultado: { area_m2: result.area.toFixed(2), indice_local_k: result.kr.toFixed(3), fator_utilizacao: result.fu.toFixed(3), fluxo_total_lm: result.phi_total.toFixed(0), numero_luminarias: result.n_min, iluminancia_resultante_lx: result.E_result.toFixed(1), potencia_total_W: result.P_total.toFixed(0), dpi_W_m2: result.DPI.toFixed(2), corrente_A: result.I_proj.toFixed(2), secao_cabo_mm2: result.secao_cabo, disjuntor_A: result.disj, }, }; }; return () => { window._VL_lastExport = null; }; }, [result, amb]); const addAmbiente = () => { const novo = newAmbiente(); setAmbientes(prev => [...prev, novo]); setSelectedIdx(ambientes.length); }; const removeAmbiente = (i) => { if (ambientes.length === 1) return; setAmbientes(prev => prev.filter((_, idx) => idx !== i)); setSelectedIdx(Math.max(0, i - 1)); }; const updateAmbiente = (updated) => { setAmbientes(prev => prev.map((a, i) => i === idx ? updated : a)); }; const okCount = resultados.filter(r => r && r.status_E === "ok").length; return (
{/* ── Abas principais (pill segmentado) ───────────────── */}
{[ { id: "calc", label: "Cálculo por ambiente", icon: "bulb" }, { id: "resumo", label: "Resumo do projeto", icon: "lux" }, ].map(t => ( ))}
{ambientes.length} ambiente{ambientes.length !== 1 ? "s" : ""} · {okCount} conforme{okCount !== 1 ? "s" : ""}
{tab === "resumo" ? ( ) : ( <> {/* ── Trilho horizontal de ambientes ─────────────── */}
{ambientes.map((a, i) => ( setSelectedIdx(i)} onRemove={() => removeAmbiente(i)} canRemove={ambientes.length > 1} /> ))}
{/* ── 2 colunas amplas: formulário + resultados ──── */}
CONFIGURAÇÃO · MÉTODO DOS LÚMENS

{amb.nome || `Ambiente ${idx + 1}`}

AUTO-CÁLCULO
)}
); }; /* Exporta para o sistema global */ window.VL_Iluminacao = Iluminacao;