/* global React */
const { useState, useEffect, useRef } = React;
// =====================================================================
// MODAL DE LOGIN (fullscreen, bloqueante)
// =====================================================================
function LoginModal({ onSuccess }) {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [err, setErr] = useState('');
const [busy, setBusy] = useState(false);
const emailRef = useRef(null);
useEffect(() => {
setTimeout(() => emailRef.current?.focus(), 100);
}, []);
const submit = async () => {
if (!email || !password) {
setErr('Informe email e senha.');
return;
}
setErr('');
setBusy(true);
try {
await window.sb.signIn(email, password);
setPassword('');
onSuccess();
} catch (e) {
setErr(e.message || 'Credenciais inválidas.');
} finally {
setBusy(false);
}
};
const onKey = (e) => {
if (e.key === 'Enter') submit();
};
return (
af
Calculadora Fiscal
Acesso restrito · Lucro Real
);
}
window.LoginModal = LoginModal;
// =====================================================================
// LOADING OVERLAY GLOBAL
// =====================================================================
function LoadingOverlay({ text }) {
return (
);
}
window.LoadingOverlay = LoadingOverlay;
// =====================================================================
// MODAL DE CONFIRMAÇÃO GENÉRICO (substitui confirm() nativo)
// =====================================================================
function ConfirmModal({ open, title, message, okLabel = 'Confirmar', cancelLabel = 'Cancelar', danger = false, onOk, onCancel }) {
useEffect(() => {
if (!open) return;
const onKey = (e) => {
if (e.key === 'Escape') onCancel?.();
if (e.key === 'Enter') onOk?.();
};
document.addEventListener('keydown', onKey);
return () => document.removeEventListener('keydown', onKey);
}, [open, onOk, onCancel]);
if (!open) return null;
return (
{message}
);
}
window.ConfirmModal = ConfirmModal;
// =====================================================================
// CLOUD HELPERS: salvar, listar, visualizar, deletar
// =====================================================================
window.cloud = {
// --- Salvar simulação ---
async saveSim(state, calc) {
const v = state.venda;
const c = calc;
const prod = state.produtos.find(p => p.id === v.produtoId);
const payload = {
produto_nome: prod?.nome || '(sem produto)',
produto_snapshot_id: null,
qtd: v.qtd || 0,
valor_total: v.valor || 0,
uf_destino: v.uf,
contribuinte: v.contrib === 'SIM',
marketplace: v.mkt === 'SIM',
comissao_pct: v.mkt === 'SIM' ? v.comissao : null,
com_ipi: v.ipi === 'SIM',
cmv_unit: +(c.cmvUn || 0).toFixed(4),
lucro_liq: +(c.dre.lucroLiq || 0).toFixed(2),
margem_liq: +(c.kpi.margemLiq || 0).toFixed(4),
carga_trib: +(c.kpi.cargaTrib || 0).toFixed(4),
tributos_total: +(c.kpi.tributos || 0).toFixed(2),
dre_snapshot: {
valorNota: c.dre.valorNota, ipi: c.dre.ipi, receitaBruta: c.dre.receitaBruta,
icmsDeb: c.dre.icmsDeb, pis: c.dre.pis, cofins: c.dre.cofins, difal: c.dre.difal,
receitaLiq: c.dre.receitaLiq, cmvTotal: c.dre.cmvTotal, lucroBruto: c.dre.lucroBruto,
comissao: c.dre.comissao, custosFixos: c.dre.custosFixos,
lucroAntesIR: c.dre.lucroAntesIR, irpj: c.dre.irpj, csll: c.dre.csll, lucroLiq: c.dre.lucroLiq,
isNaoContrib: c.isNaoContrib, baseICMS: c.baseICMS, basePisCofins: c.basePisCofins,
aliqICMS: c.aliqICMS, aliqInterna: c.aliqInterna,
},
params_snapshot: { ...state.params },
versao_payload: 1,
notas: null,
};
const data = await window.sb.insert('simulations', payload);
return data && data[0];
},
// --- Salvar versão de produto ---
async saveProduto(produto, params) {
const c = window.calcProduto(produto, params);
const ncmDoXml = (produto.nf.xmlInfo && Array.isArray(produto.nf.xmlInfo.ncms) && produto.nf.xmlInfo.ncms.length > 0)
? produto.nf.xmlInfo.ncms[0]
: null;
const payload = {
nome: produto.nome,
ncm: ncmDoXml,
mp_kg_dia: produto.prod.mpKg || 0,
qtd_dia: produto.prod.qtd || 0,
nf_valor: produto.nf.valor || 0,
nf_kg: produto.nf.kg || 0,
nf_icms: produto.nf.icms || 0,
nf_ipi: produto.nf.ipi || 0,
nf_simples: produto.nf.simples === 'SIM',
nf_frete: produto.nf.frete || 0,
nf_seguro: produto.nf.seguro || 0,
nf_outras: produto.nf.outras || 0,
nf_desconto: produto.nf.desconto || 0,
nf_xml_info: produto.nf.xmlInfo || null,
materia_prima_id: produto.nf.materiaPrimaId || null,
materia_prima_snapshot: produto.nf.materiaPrimaSnapshot || null,
insumos: (produto.insumos || []).map(i => ({
nome: i.nome, valor: i.valor || 0,
credPC: !!i.credPC, credICMS: !!i.credICMS,
aliqICMS: i.aliqICMS || 0,
nota: i.nota || '', custom: !!i.custom,
})),
cmv_calc: +(c.cmv.cmvUn || 0).toFixed(4),
versao_payload: 1,
notas: null,
};
const data = await window.sb.insert('product_versions', payload);
return data && data[0];
},
// --- Listar simulações ---
async listSims() {
return window.sb.select('simulations',
'select=id,seq,produto_nome,qtd,valor_total,uf_destino,marketplace,cmv_unit,lucro_liq,margem_liq,carga_trib,created_at&order=seq.desc&limit=200');
},
async getSim(simId) {
const arr = await window.sb.select('simulations', `id=eq.${simId}&select=*`);
if (!arr || !arr.length) throw new Error('Simulação não encontrada');
return arr[0];
},
async deleteSim(simId) {
return window.sb.del('simulations', `id=eq.${simId}`);
},
// --- Listar produtos ---
async listProds() {
return window.sb.select('product_versions',
'select=id,seq,nome,ncm,mp_kg_dia,qtd_dia,nf_valor,nf_kg,cmv_calc,created_at&order=seq.desc&limit=200');
},
async getProd(prodId) {
const arr = await window.sb.select('product_versions', `id=eq.${prodId}&select=*`);
if (!arr || !arr.length) throw new Error('Produto não encontrado');
return arr[0];
},
async deleteProd(prodId) {
return window.sb.del('product_versions', `id=eq.${prodId}`);
},
// ============================================================
// MATÉRIAS-PRIMAS — catálogo (única tabela com UPDATE permitido)
// ============================================================
async listMP() {
return window.sb.select('materias_primas',
'select=id,seq,nome,ncm,fornecedor,nf_valor,nf_kg,nf_icms,custo_kg,ultima_compra_em,updated_at&order=updated_at.desc&limit=500');
},
async getMP(mpId) {
const arr = await window.sb.select('materias_primas', `id=eq.${mpId}&select=*`);
if (!arr || !arr.length) throw new Error('Matéria-prima não encontrada');
return arr[0];
},
async createMP(mp) {
// mp = { nome, ncm, fornecedor, cnpj_fornecedor, descricao_xml, notas, nf_*, custo_kg, nf_xml_info }
const payload = window.cloud._normalizarMP(mp);
const data = await window.sb.insert('materias_primas', payload);
return data && data[0];
},
async updateMP(mpId, patch) {
// patch pode ser parcial; aceita os mesmos campos de createMP
const normalizado = window.cloud._normalizarMP(patch, true);
const data = await window.sb.update('materias_primas', `id=eq.${mpId}`, normalizado);
return data && data[0];
},
async deleteMP(mpId) {
return window.sb.del('materias_primas', `id=eq.${mpId}`);
},
// Util: normaliza objeto de MP para payload Supabase
_normalizarMP(mp, parcial = false) {
const out = {};
const set = (k, v, def) => {
if (mp[k] !== undefined) out[k] = v ?? def;
else if (!parcial && def !== undefined) out[k] = def;
};
set('nome', mp.nome, '');
set('ncm', mp.ncm || null, null);
set('fornecedor', mp.fornecedor || null, null);
set('cnpj_fornecedor', mp.cnpj_fornecedor || null, null);
set('descricao_xml', mp.descricao_xml || null, null);
set('notas', mp.notas || null, null);
set('nf_valor', +mp.nf_valor || 0, 0);
set('nf_kg', +mp.nf_kg || 0, 0);
set('nf_icms', +mp.nf_icms || 0, 18);
set('nf_ipi', +mp.nf_ipi || 0, 0);
if (mp.nf_simples !== undefined) out.nf_simples = !!mp.nf_simples;
else if (!parcial) out.nf_simples = false;
set('nf_frete', +mp.nf_frete || 0, 0);
set('nf_seguro', +mp.nf_seguro || 0, 0);
set('nf_outras', +mp.nf_outras || 0, 0);
set('nf_desconto', +mp.nf_desconto || 0, 0);
if (mp.nf_xml_info !== undefined) out.nf_xml_info = mp.nf_xml_info;
if (mp.custo_kg !== undefined) out.custo_kg = +(+mp.custo_kg).toFixed(4);
if (mp.ultima_compra_em !== undefined) out.ultima_compra_em = mp.ultima_compra_em;
return out;
},
// Calcula custo/kg de uma MP usando a mesma lógica do calcProduto
calcularCustoKgMP(mp, params) {
const N = (x) => { const n = parseFloat(x); return isNaN(n) ? 0 : n; };
if (!mp || N(mp.nf_kg) <= 0) return 0;
const nfValor = N(mp.nf_valor);
const nfKg = N(mp.nf_kg);
const aliqIcms = N(mp.nf_icms) / 100;
const aliqIpi = N(mp.nf_ipi) / 100;
const aliqPIS = N(params.pis) / 100;
const aliqCOFINS = N(params.cofins) / 100;
const frete = N(mp.nf_frete);
const seguro = N(mp.nf_seguro);
const outras = N(mp.nf_outras);
const desconto = N(mp.nf_desconto);
const despesasAcessorias = frete + seguro + outras - desconto;
const valorProd = (nfValor - despesasAcessorias) / (1 + aliqIpi);
const icmsDestac = valorProd * aliqIcms;
const basePC = (valorProd - icmsDestac) + despesasAcessorias;
const credPIS = mp.nf_simples ? 0 : basePC * aliqPIS;
const credCOFINS = mp.nf_simples ? 0 : basePC * aliqCOFINS;
const credTotal = icmsDestac + credPIS + credCOFINS;
const custoLiq = nfValor - credTotal;
return custoLiq / nfKg;
},
};