export const fxTempoSource = 'Orientação operacional expressa da Tempo,25/10/2026:compra de moeda→taxa de compra;venda→taxa de venda;operações comerciais→taxa média de referência do Banco de Moçambique na data de submissão indicada. Interpretar compra/venda pela perspectiva do boletim. Excepções fiscais específicas conservam disposição,data,taxa e âmbito;sem recálculo automático dos históricos.'; export type FxSelection = { issuer?: string; nature: '' | 'purchase' | 'sale' | 'commercial'; submissionDate: string; bulletinDate: string; bulletinRef: string; source: string; sourceCurrency: string; direction: '' | 'mzn_per_unit' | 'units_per_mzn'; bulletinPerspective: string; relation: '' | 'same' | 'opposite'; buy: string; sell: string; mean: string; mode: 'general' | 'exception'; exceptionRef: string; exceptionScope: string; exceptionDate: string; exceptionRate: string; applied?: { column: string; rate: string; date: string; source: string; selectedAt: string; selectionKey: string; }; }; export function emptyFxSelection(): FxSelection { return { nature: '', submissionDate: '', bulletinDate: '', bulletinRef: '', source: '', sourceCurrency: '', direction: '', bulletinPerspective: '', relation: '', buy: '', sell: '', mean: '', mode: 'general', exceptionRef: '', exceptionScope: '', exceptionDate: '', exceptionRate: '', }; } function date(s: string) { const d = new Date(s + 'T00:00:00Z'); return ( /^\d{4}-\d{2}-\d{2}$/.test(s) && Number.isFinite(d.getTime()) && d.toISOString().slice(0, 10) === s ); } function positive(s: string) { return /^\d{1,15}(\.\d{1,8})?$/.test(s) && Number(s) > 0; } export function fxSelectionKey(s: FxSelection): string { const { applied, ...inputs } = s; void applied; return JSON.stringify(inputs); } export function selectFx(s: FxSelection): NonNullable { if ( !['purchase', 'sale', 'commercial'].includes(s.nature) || !date(s.submissionDate) || !s.bulletinRef.trim() || !s.source.trim() || !s.sourceCurrency.trim() || !['mzn_per_unit', 'units_per_mzn'].includes(s.direction) ) throw Error( 'Indique natureza,data de submissão,moeda,direcção e boletim/fonte;sem usar taxa actual ouzero por defeito.', ); let column = '', rate = '', day = ''; if (s.mode === 'exception') { if ( !s.exceptionRef.trim() || !s.exceptionScope.trim() || !date(s.exceptionDate) || !positive(s.exceptionRate) ) throw Error( 'Excepção exige disposição fiscal,âmbito,data e taxa identificados;os restantes percursos continuam disponíveis.', ); column = 'exception'; rate = s.exceptionRate; day = s.exceptionDate; } else { if ( s.mode !== 'general' || s.bulletinDate !== s.submissionDate || !date(s.bulletinDate) ) throw Error( 'Use boletim da data de submissão indicada;ausência de boletim não autoriza usar a taxa mais recente.', ); if (s.nature === 'commercial') { let officialUrl = false; try { officialUrl = ['bancomoc.mz', 'www.bancomoc.mz'].includes( new URL(s.source).hostname, ); } catch { /* An identified official local exemplar does not require another validation. */ } if (!officialUrl && s.issuer?.trim() !== 'Banco de Moçambique') throw Error( 'Operação comercial usa taxa média de referência do Banco de Moçambique;identifique a origemBM do boletim arquivado ou a suaURL,sem exigir nova validação da fonte aceite.', ); column = 'mean'; rate = s.mean; } else { if ( !s.bulletinPerspective.trim() || !['same', 'opposite'].includes(s.relation) ) throw Error( 'Identifique a perspectiva indicada no boletim e a relação com a natureza declarada;sem inverter a operação do cliente por presunção.', ); column = (s.nature === 'purchase') === (s.relation === 'same') ? 'buy' : 'sell'; rate = column === 'buy' ? s.buy : s.sell; } day = s.submissionDate; } if (!positive(rate)) throw Error( 'Taxa da coluna aplicável ausente ou inválida;não calcular média a partir de compra/venda.', ); return { column, rate, date: day, source: `${s.bulletinRef};${s.source};perspectiva:${s.bulletinPerspective || 'média de referênciaBM'};natureza:${s.nature};relação:${s.relation || 'não aplicável à média'};${s.mode === 'exception' ? s.exceptionRef + ';' + s.exceptionScope : fxTempoSource}`, selectedAt: new Date().toISOString(), selectionKey: fxSelectionKey(s), }; }