import {useState} from 'react'; import {costRules,newNormalCosts,newCostRow,reusableCreditBalance,type CostRow,type NormalCostsInput} from '@/lib/irpc-normal-costs'; import {normalFinancialSource,type NormalBook} from '@/lib/irpc-normal'; import {fiscalMoney} from '@/lib/irpc-normal-schedules'; import {addFractions,subtractFractions,fractionPresentation} from '@/lib/fiscal-rational'; import {type ProfitTopic} from '@/lib/irpc-normal-profit'; type Field=[string,string,'money'|'text'|'choice'|'method'|'source']; const fields:Record={ allowance:[['billed','Parcela facturada a clientes','money'],['taxedNotBilled','Parcela tributada emIRPS e não facturada a clientes','money']], advertising:[['campaign','Campanha de lançamento com horizonte superior a um ano','choice']], vehicle:[['passenger','Viatura ligeira de passageiros','choice'],['publicOrRental','Afecta a serviço público de transportes ou aluguer na actividade normal','choice']], fuel:[['supportedNormal','Parcela com activo/locação e consumo normal comprovados','money'],['passenger','Viatura ligeira de passageiros','choice'],['publicOrRental','Afecta a serviço público de transportes ou aluguer na actividade normal','choice']], rental:[['nonAcceptedQuota','Parcela documentada da quota não aceite (36h)','money'],['quotaReference','Documento e regra da quota não aceite','text'],['passenger','Aplicação36(4) — ligeira de passageiros','choice'],['publicOrRental','Afecta a serviço público de transportes ou aluguer na actividade normal','choice']], stock:[['balanceKey','Identificação única do inventário/objecto','text'],['lossObserved','Perda de valor efectivamente observada','choice'],['observedLoss','Perda total observada documentada','money'],['previous','Provisão fiscal anterior que subsiste','money']], litigation:[['balanceKey','Identificação única do processo/objecto','text'],['caseOngoing','Processo judicial em curso','choice'],['chargeDeductible','Obrigação daria lugar a custo fiscal dedutível','choice'],['obligation','Obrigação coberta documentada','money'],['previous','Provisão fiscal anterior que subsiste','money']], provision_reversal:[['balanceKey','Identificação única da provisão/objecto','text'],['previous','Parcela antes fiscalmente deduzida','money'],['eventEnded','Evento não subsiste ou utilização para fim diferente','choice'],['inResult','Reversão já incluída em proveitos do resultado actual','choice']], taxed_reversal:[['balanceKey','Identificação única da provisão/objecto','text'],['previous','Parcela antes tributada e não deduzida','money'],['eventEnded','Evento não subsiste ou utilização para fim diferente','choice'],['inResult','Reversão já incluída em proveitos do resultado actual','choice']], prior_cost:[['originalYear','Exercício de origem','text'],['unknownAtClose','Imprevisível ou manifestamente desconhecido no encerramento anterior','choice'],['closingEvidence','Suporte do conhecimento no encerramento anterior','text']], prior_income:[['originalYear','Exercício de origem','text'],['unknownAtClose','Imprevisível ou manifestamente desconhecido no encerramento anterior','choice'],['closingEvidence','Suporte do conhecimento no encerramento anterior','text']], work:[['projectKey','Identificação única da obra','text'],['method','Critério fiscal da obra','method'],['price','Preço contratual estabelecido','money'],['totalCost','Custo total estimado para concluir','money'],['incurred','Custos efectivamente incorporados acumulados','money'],['priorRevenue','Proveitos fiscais reconhecidos em exercícios anteriores','money'],['priorCost','Custos fiscais reconhecidos em exercícios anteriores','money'],['costSource','Linha de custos da obra no resultado actual','source'],['bookCost','Parcela de custos da obra já contabilizada no exercício','money'],['partialBilling','Facturação parcial com grau de acabamento correspondente','choice'],['ownFractionalDelivery','Obra por conta própria vendida e entregue fraccionadamente','choice'],['consistentMethod','Critério uniforme para obras idênticas e mantido até ao final','choice'],['publicReception','Recepção provisória de obra pública','choice'],['deferredReceipt','Receita antecipada19(5) proposta — zero conhecido se não aplicada','money']], }; export function SimulationIrpcCosts({draft,book,save}:{draft:Record;book:NormalBook;save:(b:NormalBook)=>void}){ const[review,setReview]=useState(false),[error,setError]=useState(''),i=book.costs,source=normalFinancialSource(draft); if(!i)return; const patch=(p:Partial)=>{setReview(false);save({...book,costs:{...i,...p}});}; const row=(id:string,p:Partial)=>patch({rows:i.rows.map(r=>r.id===id?{...r,...p}:r)}); const fact=(r:CostRow,k:string,v:string)=>row(r.id,{facts:{...r.facts,[k]:v}}); const summaries=()=>{const groups=new Map();for(const r of book.schedules?.rows??[]){if(!r.rule)continue;const k=({tax_own:'expenseCorrections',tax_other:'expenseCorrections',undocumented:'expenseCorrections',capital_in:'positivePatrimonial',patrimonial_gain:'positivePatrimonial',capital_out:'negativePatrimonial',patrimonial_cost:'negativePatrimonial',future_cost:'timing',future_income:'timing',current_cost:'timing',current_income:'timing'}as Record)[r.rule];groups.set(k,[...(groups.get(k)??[]),r.amount]);}for(const r of i.rows)if(r.rule){const k=costRules[r.rule][1];groups.set(k,[...(groups.get(k)??[]),r.amount]);}return[...groups].map(([key,values])=>({key,amount:fractionPresentation(addFractions(values.map(fiscalMoney))).decimal}));}; const reuse=(r:CostRow)=>{try{const code=r.sourceCode,l=source?.accountingLines.find(l=>l.code===code);if(!l)throw Error('Seleccione uma origem.');const claims=[...book.rows,...(book.schedules?.rows??[]),...i.rows.filter(q=>q.id!==r.id),...i.rows.filter(q=>q.rule==='work').map(q=>({sourceCode:q.facts.costSource,amount:q.facts.bookCost}))].filter(q=>q.sourceCode===code);const available=subtractFractions(fiscalMoney(l.total),addFractions(claims.map(q=>fiscalMoney(q.amount))));if(BigInt(available.numerator)<=0n)throw Error('Não há parcela livre conhecida nesta origem.');row(r.id,{amount:fractionPresentation(available).decimal});setError('');}catch(e){setError(String(e));}}; return
Custos com limites, provisões e periodização — exercício2026

Reutiliza o resultado e as linhas já declaradas. Identifique só a parcela de cada categoria e os parâmetros em falta. Os limites anuais de publicidade e créditos são partilhados por todas as parcelas; os saldos de provisões não são consumidos. A divergência M20/M22 não bloqueia estes cálculos. Benefícios/casos especiaisL7 e saldo anualL8 mantêm os seus âmbitos.

{error&&

{error}

} {i.rows.some(r=>r.rule==='advertising')&&
Publicidade — limite único anual

Volume de vendas da origem: {source?.accountingLines.find(l=>l.code==='revenue')?.total??'Em falta'} MZN. O limite não se repete por factura.

Campanhas de lançamento com efeito superior a um ano exigem36-A e articulação própria; permanecem discriminadas, sem dedução ordinária presumida.

} {i.rows.some(r=>r.rule==='credit')&&
Créditos — limites anual e acumulado{reusableCreditBalance(source)&&}

1,5% anual e6% acumulado; saldo anterior em falta não significa zero. Não aplica regras bancárias/seguradoras nem percentagens de antiguidade presumidas.

} {i.rows.map((r,n)=>{const income=['provision_reversal','taxed_reversal','prior_income','work'].includes(r.rule),omitted=r.rule==='provision_reversal'&&r.facts.inResult==='no';return
Parcela {n+1} — {r.document||'Por identificar'} {(['document','line','amount','reference']as const).map(k=>)} {!omitted&&<>} {(fields[r.rule]??[]).filter(([k])=>!(k==='publicOrRental'&&r.facts.passenger!=='yes')&&!(k==='publicReception'&&r.facts.method!=='closing')&&!(r.rule==='stock'&&r.facts.lossObserved==='no'&&['observedLoss','previous'].includes(k))&&!(r.rule==='litigation'&&r.facts.caseOngoing==='no'&&['obligation','previous','chargeDeductible'].includes(k))).map(([k,label,type])=>)}
;})} {book.profit&&<>{review&&

Reveja as somas brutas antes de aplicar; não alteram os factos, o resultado financeiro ou os históricos.

{summaries().map(t=>

{({expenseCorrections:'Correcções aos custos',incomeCorrections:'Correcções aos proveitos',positivePatrimonial:'Variações positivas',negativePatrimonial:'Variações negativas',timing:'Periodização',special:'Casos especiais'})[t.key]}: actual {book.profit!.topics[t.key].amount||book.profit!.topics[t.key].state||'Em falta'} → mapas {t.amount} MZN

)}
}}

Ao calcular e guardar, os novos resultados entram numa versão própria; o resultado anterior permanece no histórico. Informação em falta impede apenas os totais que dependem dessa parcela.

; }