import { acquisitionScope, readAcquisitionStore } from './iva-acquisitions.ts'; export const digitalVersion = 'digital-operations/1'; export const digitalSource = 'CIVA: Lei10/2025, PDF12/p2180(12), artigos3(2)/(7),6(7)k/(8)/(9); enumeração6(7)k i–v mantida de Lei13/2016 PDF8/p1426(266). Obrigações26(1–3),2016PDF17/p1426(275), e23(2),25(7),26(4),32(1)c e33(2),2025PDF13–14. Recorte2026. Natureza, localização e responsável separados de isenção, taxa, base, IVA e dedução. Modelos/formalidades remetidos para regulamentação não são presumidos.'; export const digitalTreatments = [ { id: 'web', kind: 'service', branch: 'P121', clause: '6(7)k i', label: 'Sítios, alojamento, manutenção remota e acesso a plataformas/cloud', }, { id: 'software_service', kind: 'service', branch: 'P121', clause: '6(7)k ii/6(9)', label: 'Programa/actualização como serviço, incluindo SaaS', }, { id: 'information', kind: 'service', branch: 'P121', clause: '6(7)k iii', label: 'Imagens, textos, informação e acesso a bases de dados', }, { id: 'media', kind: 'service', branch: 'P121', clause: '6(7)k iv/6(9)', label: 'Música, filmes, jogos, emissões e streaming', }, { id: 'teaching', kind: 'service', branch: 'P121', clause: '6(7)k v', label: 'Ensino à distância por meios electrónicos', }, { id: 'analogous', kind: 'service', branch: 'P121', clause: '6(7)k vi/6(9)', label: 'Outro serviço digital análogo, financeiro ou de intermediação digital', }, { id: 'software_asset', kind: 'goods', branch: 'P130', clause: '3(2)/(7)', label: 'Software como activo digital', }, { id: 'content_asset', kind: 'goods', branch: 'P130', clause: '3(2)/(7)', label: 'Conteúdo digital como activo', }, { id: 'data_asset', kind: 'goods', branch: 'P130', clause: '3(2)/(7)', label: 'Dados digitais com finalidade económica', }, { id: 'crypto_asset', kind: 'goods', branch: 'P130', clause: '3(2)/(7)', label: 'Criptomoeda como activo digital', }, { id: 'ebook_asset', kind: 'goods', branch: 'P130', clause: '3(2)/(7)', label: 'E-book como activo digital', }, { id: 'profile_asset', kind: 'goods', branch: 'P130', clause: '3(2)/(7)', label: 'Perfil em rede social como activo', }, { id: 'access_asset', kind: 'goods', branch: 'P130', clause: '3(2)/(7)', label: 'Conta, acesso ou identificador digital equiparável', }, { id: 'other_asset', kind: 'goods', branch: 'P130', clause: '3(2)/(7)', label: 'Outro activo virtual equiparável, com natureza fundamentada', }, ] as const; type Choice = '' | 'yes' | 'no'; export type DigitalRow = { id: string; kind: string; treatment: string; description: string; period: string; origin: 'projected' | 'effective'; operationDate: string; operationRef: string; documentRef: string; documentDate: string; subjectRef: string; useRef: string; natureRef: string; scope: string; evidence: string; providerPlace: string; providerCountry: string; providerRef: string; buyerPlace: string; buyerCountry: string; buyerRef: string; buyerTaxable: string; buyerTaxRef: string; providerTaxable: string; providerTaxRef: string; representative: string; representativeRef: string; communicationPrior: string; communicationRef: string; businessUse: string; businessRef: string; goodsPlace: string; goodsPlaceRef: string; conditions: Partial>; }; type LineResult = { id: string; status: 'determined' | 'not_applicable' | 'indeterminate'; kind: string; territory: 'national' | 'outside' | null; responsible: | 'acquirer' | 'provider' | 'representative' | 'outside' | 'indeterminate'; obligationReason: string; declarationReferenceDate: string | null; procedure: 'regulation_pending' | 'general' | 'outside' | 'indeterminate'; baseAmount: null; vat: null; rate: null; deduction: null; reason: string; }; export type DigitalCalculation = { version: string; revision: number; calculatedAt: string; opportunity: string; sourceKey: string; source: string; inputs: DigitalRow[]; rows: LineResult[]; }; export type DigitalBook = { rows: DigitalRow[]; result?: DigitalCalculation; history?: DigitalCalculation[]; }; type Store = ReturnType & { digitalOperations?: Record; }; export function newDigitalRow(id: string): DigitalRow { return { id, kind: '', treatment: '', description: '', period: '', origin: 'projected', operationDate: '', operationRef: '', documentRef: '', documentDate: '', subjectRef: '', useRef: '', natureRef: '', scope: '', evidence: '', providerPlace: '', providerCountry: '', providerRef: '', buyerPlace: '', buyerCountry: '', buyerRef: '', buyerTaxable: '', buyerTaxRef: '', providerTaxable: '', providerTaxRef: '', representative: '', representativeRef: '', communicationPrior: '', communicationRef: '', businessUse: '', businessRef: '', goodsPlace: '', goodsPlaceRef: '', conditions: {}, }; } export function digitalConditions( kind: string, ): Array { return kind === 'goods' ? [ [ 'digitalAsset', 'Activo intangível em formato electrónico, com valor económico e susceptível de apropriação, titularidade, controlo, transferência ou licenciamento —3(7)', ], [ 'assetTransfer', 'Esta operação transmite o bem digital identificado; a qualificação como activo distingue-se da prestação de um serviço', ], ] : kind === 'service' ? [ [ 'digitalService', 'Prestação intangível por meios electrónicos/software/plataforma/rede, nos termos de6(9); não exige ausência total de intervenção humana', ], [ 'serviceSupply', 'A operação identificada é prestação de serviço, distinguida da transmissão de um activo digital', ], ] : []; } export function switchDigitalKind(row: DigitalRow, kind: string): DigitalRow { return { ...row, kind, treatment: '', conditions: {} }; } export function switchDigitalTreatment( row: DigitalRow, treatment: string, ): DigitalRow { return { ...row, treatment, conditions: {} }; } function readStore(draft: Record): Store { const s = readAcquisitionStore(draft.modelo_a_snapshot) as Store; if ( s.digitalOperations !== undefined && (!s.digitalOperations || typeof s.digitalOperations !== 'object' || Array.isArray(s.digitalOperations)) ) throw new Error( 'Operações digitais não recuperáveis; originais conservados.', ); const fields = Object.keys(newDigitalRow('')).filter( (k) => k !== 'conditions', ); for (const b of Object.values(s.digitalOperations ?? {})) if ( !b || !Array.isArray(b.rows) || (b.history !== undefined && !Array.isArray(b.history)) || b.rows.some( (r) => !r || fields.some( (k) => typeof r[k as keyof DigitalRow] !== 'string', ) || !r.conditions || typeof r.conditions !== 'object' || Array.isArray(r.conditions), ) ) throw new Error( 'Linhas digitais não recuperáveis; sem lista vazia por defeito.', ); return s; } export function digitalBook(draft: Record): DigitalBook { return ( readStore(draft).digitalOperations?.[acquisitionScope(draft)] ?? { rows: [], } ); } export function saveDigitalBook( draft: Record, book: DigitalBook, ): string { const s = readStore(draft), encoded = JSON.stringify({ ...s, digitalOperations: { ...s.digitalOperations, [acquisitionScope(draft)]: book, }, }); readStore({ ...draft, modelo_a_snapshot: encoded }); return encoded; } function date(value: string): boolean { const d = new Date(value + 'T00:00:00Z'); return ( /^\d{4}-\d{2}-\d{2}$/.test(value) && Number.isFinite(d.getTime()) && d.toISOString().slice(0, 10) === value ); } function declarationDate(period: string): string { const [y, m] = period.split('-').map(Number); return new Date(Date.UTC(y, m, 10)).toISOString().slice(0, 10); } export function evaluateDigital(row: DigitalRow): LineResult { const result: LineResult = { id: row.id, status: 'indeterminate', kind: row.kind, territory: null, responsible: 'indeterminate', obligationReason: 'Responsável por esclarecer; nenhuma obrigação ou quantia presumida.', declarationReferenceDate: null, procedure: 'indeterminate', baseAmount: null, vat: null, rate: null, deduction: null, reason: '', }; const stop = ( reason: string, status: LineResult['status'] = 'indeterminate', ) => ({ ...result, status, reason }); const rule = digitalTreatments.find( (t) => t.id === row.treatment && t.kind === row.kind, ); if (!rule) return stop( 'Distinga transmissão de bem digital de prestação de serviço e identifique o tratamento. Software, licenciamento ou acesso não decidem essa distinção apenas pelo nome.', ); if ( !/^2026-(0[1-9]|1[0-2])$/.test(row.period) || !['projected', 'effective'].includes(row.origin) || !date(row.operationDate) || row.operationDate.slice(0, 7) !== row.period ) return stop( 'Identifique mês2026 e data correspondente, efectivos ou pressupostos expressos.', ); const conditions = digitalConditions(row.kind); if (conditions.some(([k]) => row.conditions[k] === 'no')) return stop( 'A natureza não corresponde a este percurso digital; conservar dados e utilizar o tratamento aplicável. Não significa isenção ou IVA zero.', 'not_applicable', ); if (conditions.some(([k]) => row.conditions[k] !== 'yes')) return stop( 'Natureza digital ou distinção bem/serviço por esclarecer; categoria comercial não substitui os factos da operação.', ); if ( !row.description.trim() || !row.operationRef.trim() || !row.documentRef.trim() || !date(row.documentDate) || !row.subjectRef.trim() || !row.useRef.trim() || !row.natureRef.trim() || !row.scope.trim() || !row.evidence.trim() ) return stop( 'Identifique objecto, direitos/funcionalidade, destinatário/utilização, fundamento da natureza, operação, documento ou pressuposto, âmbito e período. Reutilize referências existentes; sem certificado ou aprovador adicional.', ); if ( !['MZ', 'foreign'].includes(row.buyerCountry) || !row.buyerPlace.trim() || !row.buyerRef.trim() ) return stop( 'Identifique estabelecimento ou domicílio do adquirente e referência; nacionalidade, moeda, IP ou servidor isolado não substituem esse facto.', ); if (row.buyerCountry === 'foreign') return { ...result, status: 'determined', territory: 'outside', responsible: 'outside', procedure: 'outside', obligationReason: '6(8): fora do âmbito territorial nacional desta operação; não determina imposto estrangeiro.', reason: `${rule.branch},${rule.clause}: adquirente estabelecido/domiciliado no estrangeiro,6(8), ainda que transmitente/prestador nacional. Não é benefício fiscal, isenção ou IVA zero.`, }; if ( !['MZ', 'foreign'].includes(row.providerCountry) || !row.providerPlace.trim() || !row.providerRef.trim() ) return stop( 'Identifique sede/estabelecimento/domicílio a partir do qual o transmitente/prestador fornece, com país e referência.', ); if (row.providerCountry === 'foreign') { result.territory = 'national'; result.status = 'determined'; result.reason = `${rule.branch},${rule.clause}: transmitente/prestador sem sede/EP/domicílio nacional a partir do qual fornece e adquirente nacional;6(7)k na redacção2025. A qualidade fiscal do adquirente não é presumida e distingue-se da localização.`; if (row.buyerTaxable === 'yes' && row.buyerTaxRef.trim()) return { ...result, responsible: 'acquirer', procedure: 'regulation_pending', declarationReferenceDate: declarationDate(row.period), obligationReason: '26(4): obrigações a cargo do adquirente sujeito passivo nacional;25(7): declaração mensal e mapa de retenções em modelos apropriados, nos termos a regulamentar.32(1)c: referência legal até dia10 do mês seguinte. Sem ajuste de calendário/prorrogação presumidos, sem submissão ou pagamento.23(2)exclui6(7)k;33(2)refere26(1–3), não substituir este percurso digital por operações isoladas.', }; if ( row.buyerTaxable === 'no' && row.buyerTaxRef.trim() && row.providerTaxable === 'yes' && row.providerTaxRef.trim() ) { if ( row.representative === 'yes' && row.representativeRef.trim() && row.communicationPrior === 'yes' && row.communicationRef.trim() ) return { ...result, responsible: 'representative', procedure: 'general', obligationReason: '26(1)/(2): representante residente identificado, com procuração/poderes e comunicação à contraparte antes da operação; responsabilidade solidária com o representado.33(2) remete26(1–3) para declaração de operações isoladas. Não aplicar automaticamente prazo digital32(1)c ou atribuir retenção a consumidor.', }; if ( row.representative === 'no' && row.businessUse === 'yes' && row.businessRef.trim() ) return { ...result, responsible: 'acquirer', procedure: 'general', obligationReason: '26(3): falta de representante e aquisição no exercício de actividade comercial, industrial ou profissional expressamente identificadas; obrigações pelo adquirente.33(2) e23(2) quanto26(3), sem substituir pelo percurso26(4) de sujeito passivo nacional ou presumir montante/prazo.', }; } return { ...result, obligationReason: row.buyerTaxable === 'no' ? '26(4) não atribui por este fundamento as obrigações ao adquirente não sujeito passivo. Conferir26(1–3): qualidade do fornecedor não residente, representante com poderes/comunicação ou aquisição no exercício de actividade. Localização nacional mantida; nenhuma retenção automaticamente imputada ao consumidor.' : 'Qualidade fiscal do adquirente ou referência por esclarecer; localização nacional mantida. Não atribuir automaticamente26(4) nem obrigação de retenção a consumidor.', }; } if (row.kind === 'goods') { if ( !['MZ', 'foreign'].includes(row.goodsPlace) || !row.goodsPlaceRef.trim() ) return stop( 'Bem digital equiparado a corpóreo3(2)/(7): para o percurso doméstico6(1), identificar onde se encontra na colocação à disposição, com fundamento próprio. Não inferir do servidor, nacionalidade ou domicílio do adquirente.', ); result.territory = row.goodsPlace === 'MZ' ? 'national' : 'outside'; if (result.territory === 'outside') return { ...result, status: 'determined', responsible: 'outside', procedure: 'outside', obligationReason: '6(1): localização exterior expressamente fundamentada; sem imposto estrangeiro presumido.', reason: `P130,3(2)/(7) e6(1): local do bem na colocação à disposição identificado fora do território; classificação não concede isenção.`, }; } else result.territory = 'national'; return { ...result, status: 'determined', responsible: row.providerTaxable === 'yes' && !!row.providerTaxRef.trim() ? 'provider' : 'indeterminate', procedure: row.providerTaxable === 'yes' && !!row.providerTaxRef.trim() ? 'general' : 'indeterminate', obligationReason: row.providerTaxable === 'yes' && !!row.providerTaxRef.trim() ? 'Transmitente/prestador sujeito passivo nacional: obrigações gerais identificadas23(1)/25.26(4) e prazo digital32(1)c não se aplicam por este fundamento. Sem cálculo, submissão ou pagamento.' : 'Qualidade fiscal do transmitente/prestador ou fundamento por esclarecer; não atribuir obrigação automaticamente.', reason: `${rule.branch},${rule.clause}: operação doméstica;${row.kind === 'service' ? 'localização6(3), estabelecimento nacional a partir do qual o serviço é prestado' : 'localização6(1), bem na colocação à disposição em Moçambique segundo referência expressa'}. Não confirma isenção, taxa, base, IVA ou dedução.`, }; } function key(draft: Record, book: DigitalBook): string { return JSON.stringify({ version: digitalVersion, scope: acquisitionScope(draft), location: draft.selection_labels ?? {}, rows: book.rows, }); } export function digitalCurrent( draft: Record, book: DigitalBook, ): boolean { return !!book.result && book.result.sourceKey === key(draft, book); } export function recordDigital( draft: Record, book: DigitalBook, ): DigitalBook { if ( !book.rows.length || new Set(book.rows.map((r) => r.id)).size !== book.rows.length ) throw new Error( 'Identifique operações distintas; originais conservados.', ); const result: DigitalCalculation = { version: digitalVersion, revision: (book.result?.revision ?? 0) + 1, calculatedAt: new Date().toISOString(), opportunity: acquisitionScope(draft), sourceKey: key(draft, book), source: digitalSource, inputs: structuredClone(book.rows), rows: book.rows.map(evaluateDigital), }; const next = { ...book, result, history: [ ...(book.history ?? []), ...(book.result ? [book.result] : []), ], }; saveDigitalBook(draft, next); return next; } export function digitalReportRows( draft: Record, ): Array<[string, string, string, string]> { try { const b = digitalBook(draft), r = b.result; if (!r) return []; return r.inputs.map((row, i) => { const line = r.rows[i], rule = digitalTreatments.find((t) => t.id === row.treatment); return [ 'IVA mensal', `${rule?.branch ?? 'Digital'} — ${row.period} — ${row.id}`, `${row.description};natureza:${row.kind === 'goods' ? 'Bem digital' : 'Serviço digital'};localização:${line.territory === 'national' ? 'Moçambique' : line.territory === 'outside' ? 'Fora do território' : 'Indeterminada'};${line.reason};responsável:${line.responsible};${line.obligationReason};${line.declarationReferenceDate ? 'Data de referência32(1)c:' + line.declarationReferenceDate + ';' : ''}base/IVA/dedução:Indeterminados`, `${r.opportunity};${digitalCurrent(draft, b) ? 'Resultado actual' : 'Entradas alteradas;resultado histórico conservado'};versão${r.revision};${r.calculatedAt};${r.source};${row.origin === 'projected' ? 'Pressupostos projectados' : 'Dados efectivos declarados'};${rule?.label ?? ''};operação${row.operationRef},${row.operationDate};objecto${row.subjectRef};natureza${row.natureRef};utilização${row.useRef};fornecedor${row.providerPlace},${row.providerCountry},${row.providerRef};adquirente${row.buyerPlace},${row.buyerCountry},${row.buyerRef};${row.buyerCountry === 'MZ' && row.providerCountry === 'foreign' ? 'qualidade fiscal adquirente' + row.buyerTaxable + ',' + row.buyerTaxRef + ';' : ''}${row.buyerCountry === 'MZ' && row.providerCountry === 'MZ' ? 'qualidade fiscal fornecedor' + row.providerTaxable + ',' + row.providerTaxRef + ';' : ''}${row.kind === 'goods' && row.buyerCountry === 'MZ' && row.providerCountry === 'MZ' ? 'local do bem6(1)' + row.goodsPlace + ',' + row.goodsPlaceRef + ';' : ''}${row.buyerCountry === 'MZ' && row.providerCountry === 'foreign' && row.buyerTaxable === 'no' ? 'fornecedor não residente sujeito passivo' + row.providerTaxable + ',' + row.providerTaxRef + ';representante' + row.representative + ',' + row.representativeRef + ';comunicação anterior' + row.communicationPrior + ',' + row.communicationRef + ';actividade comercial/industrial/profissional' + row.businessUse + ',' + row.businessRef + ';' : ''}âmbito${row.scope};documento/pressuposto${row.documentRef},${row.documentDate};factos${row.evidence};${digitalConditions( row.kind, ) .map( ([k, label]) => label + ':' + (row.conditions[k] ?? 'Por esclarecer'), ) .join( ';', )};sem lançamento fiscal, dedução/campo11, regularização ou totaisModeloA.`, ]; }); } catch { return [ [ 'IVA mensal', 'Digital', 'Dados não recuperáveis', 'Originais conservados; nenhuma classificação por defeito.', ], ]; } }