import { modeloAFields, modeloAReference } from './iva-modelo-a.ts'; export type FiscalFormKind = 'iva' | 'irpc'; export const fiscalFormVersion = 'investmoz-fiscal-preparation/1'; export const fiscalForms = { iva: { title: 'IVA — Modelo A', source: modeloAReference.file, hash: modeloAReference.sha256, coverage: 'Campos 01–27 do exemplar, para preenchimento manual. A edição e as divergências já documentadas continuam identificadas.', }, irpc: { title: 'IRPC — Modelo 22', source: 'IRPC M-22- DECLARACAO DE RENDIMENTOS.pdf', hash: '4b61bd83d1fbcfd3b7b5c5523cea5923f7d0f65dc4c004eff58e5db38f763c88', coverage: 'Preparação parcial: resultado, lucro, matéria colectável e liquidação nos campos abaixo. Os restantes ajustamentos e anexos do exemplar não são preenchidos aqui. L8 permanece parcial.', }, } as const; const irpcFields = [ ['200', 'Resultado do exercício'], ['259', 'Prejuízo fiscal'], ['260', 'Lucro tributável'], ['269', 'Prejuízo fiscal — regime geral'], ['270', 'Lucro tributável — regime geral'], ['276', 'Prejuízos fiscais deduzidos'], ['277', 'Benefícios fiscais a deduzir'], ['278', 'Matéria colectável — regime geral'], ['300', 'Imposto à taxa normal'], ['301', 'Imposto à taxa reduzida'], ['302', 'Colecta total'], ['303', 'Dupla tributação económica'], ['304', 'Dupla tributação internacional'], ['305', 'Benefícios fiscais'], ['306', 'Pagamento especial por conta'], ['308', 'Total das deduções'], ['309', 'IRPC liquidado'], ['310', 'Retenções na fonte'], ['311', 'Pagamentos por conta'], ['312', 'IRPC a pagar antes de juros'], ['313', 'IRPC a recuperar antes de juros'], ['314', 'Juros compensatórios'], ['315', 'IRPC a pagar'], ['316', 'IRPC a recuperar'], ] as const; export function fiscalFormFields(kind: FiscalFormKind) { return kind === 'iva' ? modeloAFields.map((f) => ({ code: f.field, label: f.purpose, guidance: f.basis, })) : irpcFields.map(([code, label]) => ({ code, label, guidance: Number(code) < 269 ? 'Quadro 10 do exemplar.' : Number(code) < 300 ? 'Quadro 11, regime geral. Outros regimes não incluídos nesta preparação.' : 'Quadro 12 do exemplar. Preenchimento manual, sem cálculo ou validação automática da liquidação.', })); } export type FiscalPreparation = { version: typeof fiscalFormVersion; kind: FiscalFormKind; sourceHash: string; name: string; nuit: string; period: string; notes: string; values: Record; }; export function newFiscalPreparation(kind: FiscalFormKind): FiscalPreparation { return { version: fiscalFormVersion, kind, sourceHash: fiscalForms[kind].hash, name: '', nuit: '', period: '', notes: '', values: {}, }; } export function readFiscalPreparation(raw: string): FiscalPreparation { if (raw.length > 100_000) throw new Error('O ficheiro excede o tamanho de uma preparação.'); let p: unknown; try { p = JSON.parse(raw); } catch { throw new Error( 'Não foi possível ler este ficheiro de preparação. Os campos actuais foram preservados.', ); } if (!p || typeof p !== 'object' || Array.isArray(p)) throw new Error('Ficheiro de preparação inválido.'); const v = p as Record; if ( v.version !== fiscalFormVersion || (v.kind !== 'iva' && v.kind !== 'irpc') || v.sourceHash !== fiscalForms[v.kind].hash ) throw new Error( 'Versão ou exemplar diferente. A preparação actual foi preservada.', ); for (const key of ['name', 'nuit', 'period', 'notes']) { if (typeof v[key] !== 'string' || v[key].length > 5000) throw new Error('Identificação ou notas inválidas.'); } if (!v.values || typeof v.values !== 'object' || Array.isArray(v.values)) throw new Error('Campos inválidos.'); const allowed = new Set(fiscalFormFields(v.kind).map((f) => f.code)); const values: Record = {}; for (const [code, value] of Object.entries(v.values)) { if ( !allowed.has(code) || typeof value !== 'string' || value.length > 100 ) throw new Error( 'Campo não reconhecido. A preparação actual foi preservada.', ); values[code] = value; } return { ...newFiscalPreparation(v.kind), name: v.name as string, nuit: v.nuit as string, period: v.period as string, notes: v.notes as string, values, }; } export function fiscalPreparationIssues(p: FiscalPreparation): string[] { const issues: string[] = []; if (!p.name.trim()) issues.push('Indique o nome ou designação social.'); if (!/^\d{9}$/.test(p.nuit)) issues.push('Indique o NUIT com 9 algarismos.'); if ( !(p.kind === 'iva' ? /^\d{4}-(0[1-9]|1[0-2])$/ : /^\d{4}$/).test( p.period, ) ) issues.push( p.kind === 'iva' ? 'Indique ano e mês do período.' : 'Indique o ano do exercício.', ); for (const f of fiscalFormFields(p.kind)) { const value = p.values[f.code] ?? ''; if (value !== '' && !/^-?\d{1,24}(?:[.,]\d{1,2})?$/.test(value)) issues.push( `Campo ${f.code}: use um montante sem separador de milhares, com até duas casas decimais.`, ); } return issues; }