import { inheritSimulatorFields } from './context-inheritance.ts'; import { landStatusVisible } from './investment-land-preparation.ts'; import { csvCell } from './csv.js'; import { fiscalQaRows } from './fiscal-qa-preview.ts'; import { isApiSimulationResultSnapshotV1 } from './simulation-contract.ts'; export type SimulationDraft = Record; export const projectQuestionnaireVersion = 'investmoz-project-dossier-2026-10-05.1'; function isProjectGuidedJourney(draft: SimulationDraft): boolean { return ( draft.purpose === 'simulate_only' && ['small_business', 'investor'].includes(String(draft.journey_type)) ); } function isInvestorProjectJourney(draft: SimulationDraft): boolean { return ( draft.purpose === 'simulate_only' && draft.journey_type === 'investor' ); } export function getFiscalPeriodRange( draft: SimulationDraft, ): { start: string; end: string } | null { const yearValue = String(draft.fiscal_period ?? ''); if (!/^\d{4}$/.test(yearValue)) return null; const year = Number(yearValue); if (draft.fiscal_periodicity === 'annual') return { start: `${yearValue}-01-01`, end: `${yearValue}-12-31` }; if (draft.fiscal_periodicity === 'one_off') { const date = String(draft.fiscal_reference_date ?? ''); const parsed = new Date(`${date}T00:00:00.000Z`); return /^\d{4}-\d{2}-\d{2}$/.test(date) && Number.isFinite(parsed.getTime()) && parsed.toISOString().slice(0, 10) === date && date.startsWith(`${yearValue}-`) ? { start: date, end: date } : null; } if (draft.fiscal_periodicity === 'monthly') { const month = String(draft.fiscal_month ?? ''); if (!/^(0[1-9]|1[0-2])$/.test(month)) return null; const endDay = new Date(Date.UTC(year, Number(month), 0)).getUTCDate(); return { start: `${yearValue}-${month}-01`, end: `${yearValue}-${month}-${String(endDay).padStart(2, '0')}`, }; } if (draft.fiscal_periodicity === 'quarterly') { const quarter = String(draft.fiscal_quarter ?? ''); const startMonth = ( { Q1: 1, Q2: 4, Q3: 7, Q4: 10 } as Record )[quarter]; if (!startMonth) return null; const endMonth = startMonth + 2; const endDay = new Date(Date.UTC(year, endMonth, 0)).getUTCDate(); return { start: `${yearValue}-${String(startMonth).padStart(2, '0')}-01`, end: `${yearValue}-${String(endMonth).padStart(2, '0')}-${String(endDay).padStart(2, '0')}`, }; } return null; } export const simulationSections = [ { id: 'purpose', title: 'Finalidade' }, { id: 'promoter', title: 'Perfil do promotor' }, { id: 'identity', title: 'Identificação e contactos' }, { id: 'project', title: 'Projecto ou negócio' }, { id: 'location', title: 'Localização' }, { id: 'financial', title: 'Estrutura financeira' }, { id: 'impact', title: 'Emprego e impacto' }, { id: 'tax', title: 'Operação fiscal' }, { id: 'opportunity', title: 'Necessidade de negócio' }, { id: 'documents', title: 'Documentos' }, { id: 'review', title: 'Revisão' }, ] as const; /** Keep the anonymous arithmetic journey short; formal submissions retain the full questionnaire. */ export function getSimulationSections(draft: SimulationDraft) { if (isProjectGuidedJourney(draft)) { if (draft.journey_type === 'small_business') { return [ { id: 'purpose', title: 'Ideia e objectivos' }, { id: 'market', title: 'Clientes e mercado' }, { id: 'location', title: 'Localização e recursos' }, { id: 'team', title: 'Equipa e execução' }, { id: 'financial', title: 'Investimento e financiamento' }, { id: 'review', title: 'Resultados, riscos e plano' }, ]; } if (draft.journey_type === 'investor') { return [ ...simulationSections.filter((section) => [ 'purpose', 'project', 'location', 'financial', 'tax', ].includes(section.id), ), { id: 'scenarios', title: 'Cenários e retorno indicativo' }, { id: 'procedures', title: 'Procedimentos e documentos' }, { id: 'review', title: 'Revisão' }, ]; } } if (draft.purpose === 'simulate_only') { return simulationSections.filter((section) => ['purpose', 'tax', 'review'].includes(section.id), ); } const journeys: Record = { personal: ['purpose', 'financial', 'tax', 'review'], small_business: [ 'purpose', 'promoter', 'project', 'financial', 'tax', 'impact', 'review', ], company: simulationSections.map((section) => section.id), investor: [ 'purpose', 'promoter', 'identity', 'project', 'location', 'financial', 'opportunity', 'documents', 'review', ], }; if (draft.journey_type && journeys[String(draft.journey_type)]) { return simulationSections.filter((section) => journeys[String(draft.journey_type)].includes(section.id), ); } return simulationSections; } export const advancedSimulationFields = new Set([ 'legal_form', 'nuit', 'registration_number', 'representative_name', 'representative_capacity', 'address', 'existing_project_id', 'expected_start_date', 'project_duration_years', 'latitude', 'longitude', 'area_required', 'area_unit', 'special_zone_id', 'infrastructure_needs', 'secured_amount', 'sought_amount', 'annual_revenue', 'annual_operating_costs', 'expected_profit', 'financing_term_months', 'funds_origin_countries', 'direct_jobs', 'indirect_jobs', 'national_jobs', 'foreign_jobs', 'skills_needed', 'local_content_percentage', 'exports_value', 'import_substitution_value', 'environmental_category', 'community_benefits', 'sales_taxable', 'sales_exempt', 'sales_zero_rate', 'exports', 'deductible_costs', 'non_deductible_costs', 'carried_losses', 'payroll', 'outbound_payments', 'imports', 'claimed_benefits', 'required_documents', ]); export const simulationOptions: Record = { journey_type: ['personal', 'small_business', 'company', 'investor'], detail_level: ['simple', 'advanced'], purpose: [ 'simulate_only', 'own_investment', 'seek_investor', 'seek_partner', 'present_opportunity', 'request_facilitation', ], manifestation_type: ['investment', 'business'], person_type: ['singular', 'collective', 'sole_trader', 'consortium'], investor_origin: ['national', 'foreign', 'mixed'], entity_stage: ['existing', 'being_formed'], preferred_language: ['pt-MZ', 'en'], preferred_channel: ['portal', 'email', 'phone'], project_type: [ 'new', 'expansion', 'acquisition', 'partnership', 'concession', 'supplier', 'buyer', 'other', ], maturity_stage: [ 'idea', 'concept', 'pre_feasibility', 'feasibility', 'financing', 'implementation', 'operation', ], land_status: ['owned', 'duat', 'leased', 'requested', 'unknown'], environmental_category: ['A', 'B', 'C', 'por_determinar'], offer_or_demand: ['offer', 'demand', 'both'], opportunity_type: [ 'capital', 'partner', 'buyer', 'supplier', 'land', 'technology', 'licence', 'services', 'other', ], confidentiality: [ 'public', 'registered_users', 'restricted', 'confidential', ], preferred_publication: ['yes', 'no'], residency_status: ['resident', 'non_resident'], fiscal_scenario_mode: [ 'approved_parameters', 'custom_planning', 'no_calculation', ], fiscal_period: Array.from( { length: new Date().getFullYear() - 1999 + 2 }, (_, index) => String(2000 + index), ), fiscal_periodicity: [ 'monthly', 'quarterly', 'annual', 'one_off', 'unknown', ], preparedness_status: ['yes', 'no', 'partial', 'unknown', 'not_applicable'], funding_status: [ 'confirmed', 'in_process', 'not_started', 'unknown', 'not_applicable', ], fiscal_month: Array.from({ length: 12 }, (_, index) => String(index + 1).padStart(2, '0'), ), fiscal_quarter: ['Q1', 'Q2', 'Q3', 'Q4'], irps_salary_view: [ 'monthly_net_estimate', 'monthly_withholding', 'annual_assessment', ], fiscal_component: [ 'irpc', 'iva', 'ispc', 'irps', 'inss', 'withholding', 'customs_ice', ], fiscal_income_category: [ 'employment', 'rental', 'digital', 'capital_gain', 'business', 'other', 'unknown', ], fiscal_residency_assessment: [ 'self_reported_resident', 'self_reported_non_resident', 'unknown', ], fiscal_operation: [ 'domestic', 'non_resident', 'import', 'export', 'refund', 'other', 'unknown', ], fiscal_documentation: [ 'documented', 'partly_documented', 'not_documented', 'unknown', ], fiscal_refund_state: [ 'estimate_only', 'request_filed', 'recognized', 'received', 'unknown', ], document_types: [ 'identity_document', 'company_registration', 'nuit_certificate', 'company_profile', 'business_plan', 'feasibility_study', 'proof_of_funds', 'special_zone_certificate', 'licence', 'site_map', ], area_unit: ['m2', 'ha', 'km2'], }; /** Move the active index in a non-wrapping searchable choice list. */ export function moveSearchChoiceIndex( activeIndex: number, direction: -1 | 1, optionCount: number, ): number { if (!Number.isSafeInteger(optionCount) || optionCount <= 0) return -1; if (!Number.isSafeInteger(activeIndex) || activeIndex < 0) return direction > 0 ? 0 : optionCount - 1; return Math.max(0, Math.min(optionCount - 1, activeIndex + direction)); } export const simulationFieldGroups: Record< string, { key: string; label: string; type?: 'number' | 'date' | 'email' | 'textarea'; required?: boolean | ((draft: SimulationDraft) => boolean); options?: string; multiple?: boolean; showWhen?: (draft: SimulationDraft) => boolean; }[] > = { purpose: [ { key: 'journey_type', label: 'O que pretende calcular ou planear? Escolha o percurso', options: 'journey_type', required: true, }, { key: 'detail_level', label: 'Nível de detalhe', options: 'detail_level', required: true, }, { key: 'purpose', label: 'O que pretende fazer?', options: 'purpose', required: true, }, { key: 'manifestation_type', label: 'Tipo de manifestação', options: 'manifestation_type', required: true, showWhen: (d) => d.purpose !== 'simulate_only', }, { key: 'preferred_language', label: 'Idioma preferido', options: 'preferred_language', required: true, }, ], promoter: [ { key: 'person_type', label: 'Tipo de pessoa', options: 'person_type', required: true, showWhen: (d) => d.purpose !== 'simulate_only', }, { key: 'investor_origin', label: 'Origem do investidor', options: 'investor_origin', required: true, showWhen: (d) => d.purpose !== 'simulate_only', }, { key: 'entity_stage', label: 'Situação da entidade', options: 'entity_stage', required: true, showWhen: (d) => d.purpose !== 'simulate_only' && ['collective', 'consortium'].includes(String(d.person_type)), }, { key: 'legal_form', label: 'Forma jurídica (conforme o registo)', required: true, showWhen: (d) => d.purpose !== 'simulate_only' && d.entity_stage === 'existing', }, { key: 'nuit', label: 'NUIT', showWhen: (d) => d.purpose !== 'simulate_only', }, ], identity: [ { key: 'legal_name', label: 'Nome / denominação', required: true, showWhen: (d) => d.purpose !== 'simulate_only', }, { key: 'commercial_name', label: 'Nome comercial', showWhen: (d) => d.purpose !== 'simulate_only', }, { key: 'registration_number', label: 'Número de registo', required: true, showWhen: (d) => d.entity_stage === 'existing' && d.purpose !== 'simulate_only', }, { key: 'representative_name', label: 'Nome do representante', required: true, showWhen: (d) => ['collective', 'consortium'].includes(String(d.person_type)) && d.purpose !== 'simulate_only', }, { key: 'email', label: 'Correio electrónico', type: 'email', required: true, showWhen: (d) => d.purpose !== 'simulate_only', }, { key: 'phone', label: 'Telefone', required: true, showWhen: (d) => d.purpose !== 'simulate_only', }, { key: 'address', label: 'Endereço', type: 'textarea', required: true, showWhen: (d) => d.purpose !== 'simulate_only', }, { key: 'preferred_channel', label: 'Canal de contacto', options: 'preferred_channel', required: true, showWhen: (d) => d.purpose !== 'simulate_only', }, ], project: [ { key: 'project_title', label: 'Título do projecto', showWhen: (d) => d.purpose !== 'simulate_only' || ['small_business', 'investor'].includes(String(d.journey_type)), }, { key: 'project_summary', label: 'Resumo executivo', type: 'textarea', showWhen: (d) => d.purpose !== 'simulate_only' || ['small_business', 'investor'].includes(String(d.journey_type)), }, { key: 'project_problem', label: 'Que problema ou necessidade pretende resolver?', type: 'textarea', showWhen: (d) => d.journey_type === 'small_business' && d.purpose === 'simulate_only', }, { key: 'target_customers', label: 'Quem são os clientes ou beneficiários?', type: 'textarea', showWhen: (d) => d.purpose === 'simulate_only' && ['small_business', 'investor'].includes(String(d.journey_type)), }, { key: 'value_proposition', label: 'Que solução ou proposta de valor oferece?', type: 'textarea', showWhen: (d) => d.purpose === 'simulate_only' && ['small_business', 'investor'].includes(String(d.journey_type)), }, { key: 'market_assumption', label: 'Hipótese sobre mercado, concorrência ou procura', type: 'textarea', showWhen: (d) => d.purpose === 'simulate_only' && ['small_business', 'investor'].includes(String(d.journey_type)), }, { key: 'market_source', label: 'Origem e data da informação de mercado (se conhecida)', showWhen: (d) => d.journey_type === 'small_business' && d.purpose === 'simulate_only', }, { key: 'project_type', label: 'Tipo de projecto', options: 'project_type', required: (d) => d.purpose !== 'simulate_only', showWhen: (d) => d.purpose !== 'simulate_only' || ['small_business', 'investor'].includes(String(d.journey_type)), }, { key: 'project_type_other', label: 'Especifique o tipo de projecto', required: true, showWhen: (d) => d.purpose !== 'simulate_only' && d.project_type === 'other', }, { key: 'sector_id', label: 'Sector de actividade', options: 'sector', required: (d) => d.purpose !== 'simulate_only', showWhen: (d) => d.purpose !== 'simulate_only' || ['small_business', 'investor'].includes(String(d.journey_type)), }, { key: 'activity_id', label: 'Actividade económica', options: 'activity', required: (d) => d.purpose !== 'simulate_only', showWhen: (d) => d.purpose !== 'simulate_only' || ['small_business', 'investor'].includes(String(d.journey_type)), }, { key: 'products_services', label: 'Produtos e serviços', type: 'textarea', showWhen: (d) => d.purpose !== 'simulate_only' || ['small_business', 'investor'].includes(String(d.journey_type)), }, { key: 'maturity_stage', label: 'Fase de maturidade', options: 'maturity_stage', required: (d) => d.purpose !== 'simulate_only', showWhen: (d) => d.purpose !== 'simulate_only' || ['small_business', 'investor'].includes(String(d.journey_type)), }, { key: 'expected_start_date', label: 'Data prevista de início', type: 'date', showWhen: (d) => d.purpose !== 'simulate_only' || ['small_business', 'investor'].includes(String(d.journey_type)), }, { key: 'project_duration_years', label: 'Duração prevista (anos)', type: 'number', showWhen: (d) => d.purpose !== 'simulate_only' || ['small_business', 'investor'].includes(String(d.journey_type)), }, { key: 'existing_project_id', label: 'Referência do projecto existente', showWhen: (d) => d.project_type === 'expansion', }, ], market: [ { key: 'customer_validation_status', label: 'Já conversou com potenciais clientes sobre esta necessidade?', options: 'preparedness_status', showWhen: (d) => d.purpose === 'simulate_only' && ['small_business', 'investor'].includes(String(d.journey_type)), }, { key: 'target_customers', label: 'Quem são os clientes ou beneficiários? Onde estão e que necessidade têm?', type: 'textarea', showWhen: (d) => d.purpose === 'simulate_only' && ['small_business', 'investor'].includes(String(d.journey_type)), }, { key: 'market_assumption', label: 'O que sabe ou está a assumir sobre procura, preço e concorrência?', type: 'textarea', showWhen: (d) => d.purpose === 'simulate_only' && ['small_business', 'investor'].includes(String(d.journey_type)), }, { key: 'market_source', label: 'Qual é a origem e a data dessa informação, se as conhecer?', showWhen: (d) => d.purpose === 'simulate_only' && ['small_business', 'investor'].includes(String(d.journey_type)), }, { key: 'competitor_assumption', label: 'Que alternativas ou concorrentes os clientes usam actualmente?', type: 'textarea', showWhen: (d) => d.purpose === 'simulate_only' && ['small_business', 'investor'].includes(String(d.journey_type)), }, { key: 'sales_channel_assumption', label: 'Como pretende chegar aos clientes e receber encomendas?', type: 'textarea', showWhen: (d) => d.purpose === 'simulate_only' && ['small_business', 'investor'].includes(String(d.journey_type)), }, { key: 'marketing_approach', label: 'Que acções de divulgação pretende experimentar?', type: 'textarea', showWhen: (d) => d.purpose === 'simulate_only' && ['small_business', 'investor'].includes(String(d.journey_type)), }, ], location: [ { key: 'location_assessment_status', label: 'Já avaliou a adequação desta localização?', options: 'preparedness_status', showWhen: (d) => ['small_business', 'investor'].includes(String(d.journey_type)), }, { key: 'province_id', label: 'Província', options: 'province', required: (d) => d.purpose !== 'simulate_only', showWhen: (d) => d.purpose !== 'simulate_only' || ['small_business', 'investor'].includes(String(d.journey_type)), }, { key: 'district_id', label: 'Distrito', options: 'district', required: (d) => d.purpose !== 'simulate_only', showWhen: (d) => d.purpose !== 'simulate_only' || ['small_business', 'investor'].includes(String(d.journey_type)), }, { key: 'admin_post_id', label: 'Posto administrativo', options: 'admin_post', showWhen: (d) => d.purpose !== 'simulate_only' || ['small_business', 'investor'].includes(String(d.journey_type)), }, { key: 'locality_id', label: 'Localidade', options: 'locality', showWhen: (d) => d.purpose !== 'simulate_only' || ['small_business', 'investor'].includes(String(d.journey_type)), }, { key: 'site_known', label: 'Local conhecido?', options: 'yes_no', required: (d) => d.purpose !== 'simulate_only', showWhen: (d) => d.purpose !== 'simulate_only' || isInvestorProjectJourney(d), }, { key: 'location_rationale', label: 'Porque considera esta localização e o que ainda precisa de confirmar?', type: 'textarea', showWhen: (d) => ['small_business', 'investor'].includes(String(d.journey_type)), }, { key: 'land_status', label: 'Situação do terreno', options: 'land_status', showWhen: (d) => landStatusVisible(d) && (d.purpose !== 'simulate_only' || isInvestorProjectJourney(d)), }, { key: 'area_required', label: 'Área necessária', type: 'number', showWhen: (d) => d.purpose !== 'simulate_only' || isInvestorProjectJourney(d), }, { key: 'area_unit', label: 'Unidade da área', options: 'area_unit', showWhen: (d) => d.purpose !== 'simulate_only' || isInvestorProjectJourney(d), }, { key: 'latitude', label: 'Latitude', type: 'number', showWhen: (d) => d.site_known === 'yes', }, { key: 'longitude', label: 'Longitude', type: 'number', showWhen: (d) => d.site_known === 'yes', }, { key: 'special_zone_id', label: 'Zona especial ZEE/ZFI', showWhen: (d) => d.purpose !== 'simulate_only' || isInvestorProjectJourney(d), }, { key: 'infrastructure_needs', label: 'Necessidades de infra-estrutura', options: 'infrastructure', multiple: true, showWhen: (d) => d.purpose !== 'simulate_only' || isInvestorProjectJourney(d), }, ], financial: [ { key: 'cost_estimate_status', label: 'Já identificou os principais custos e respectiva periodicidade?', options: 'preparedness_status', showWhen: (d) => ['small_business', 'investor'].includes(String(d.journey_type)), }, { key: 'receivables_timing_status', label: 'Já sabe quando as vendas serão recebidas?', options: 'preparedness_status', showWhen: (d) => ['small_business', 'investor'].includes(String(d.journey_type)), }, { key: 'funding_status', label: 'Qual é o estado dos recursos de financiamento de que precisa?', options: 'funding_status', showWhen: (d) => ['small_business', 'investor'].includes(String(d.journey_type)), }, { key: 'currency', label: 'Moeda', options: 'currency', required: (d) => d.purpose !== 'simulate_only', showWhen: (d) => d.purpose !== 'simulate_only' || isInvestorProjectJourney(d), }, { key: 'total_investment', label: 'Investimento total', type: 'number', required: (d) => d.purpose !== 'simulate_only', showWhen: (d) => d.purpose !== 'simulate_only' || isInvestorProjectJourney(d), }, { key: 'own_capital', label: 'Capital próprio', type: 'number', required: (d) => d.purpose !== 'simulate_only', showWhen: (d) => d.purpose !== 'simulate_only' || isInvestorProjectJourney(d), }, { key: 'domestic_debt', label: 'Financiamento interno', type: 'number', required: (d) => d.purpose !== 'simulate_only', showWhen: (d) => d.purpose !== 'simulate_only' || isInvestorProjectJourney(d), }, { key: 'foreign_debt', label: 'Financiamento externo', type: 'number', required: (d) => d.purpose !== 'simulate_only', showWhen: (d) => d.purpose !== 'simulate_only' || isInvestorProjectJourney(d), }, { key: 'grants', label: 'Subvenções', type: 'number', required: (d) => d.purpose !== 'simulate_only', showWhen: (d) => d.purpose !== 'simulate_only' || isInvestorProjectJourney(d), }, { key: 'secured_amount', label: 'Montante mobilizado', type: 'number', required: (d) => d.purpose !== 'simulate_only', showWhen: (d) => d.purpose !== 'simulate_only' || isInvestorProjectJourney(d), }, { key: 'sought_amount', label: 'Montante procurado', type: 'number', showWhen: (d) => d.purpose !== 'simulate_only' || isInvestorProjectJourney(d), }, { key: 'annual_revenue', label: 'Receitas anuais estimadas', type: 'number', showWhen: (d) => d.purpose !== 'simulate_only' || isInvestorProjectJourney(d), }, { key: 'annual_operating_costs', label: 'Custos operacionais anuais', type: 'number', showWhen: (d) => d.purpose !== 'simulate_only' || isInvestorProjectJourney(d), }, { key: 'expected_profit', label: 'Lucro esperado', type: 'number', showWhen: (d) => d.purpose !== 'simulate_only', }, { key: 'financing_term_months', label: 'Prazo de financiamento (meses)', type: 'number', showWhen: (d) => Number(d.domestic_debt) + Number(d.foreign_debt) > 0, }, { key: 'funds_origin_countries', label: 'Países de origem dos fundos', options: 'country', multiple: true, showWhen: (d) => Number(d.foreign_debt) > 0, }, ], impact: [ { key: 'direct_jobs', label: 'Empregos directos', type: 'number', showWhen: (d) => d.purpose !== 'simulate_only', }, { key: 'indirect_jobs', label: 'Empregos indirectos', type: 'number', showWhen: (d) => d.purpose !== 'simulate_only', }, { key: 'national_jobs', label: 'Empregos nacionais', type: 'number', showWhen: (d) => d.purpose !== 'simulate_only', }, { key: 'foreign_jobs', label: 'Empregos estrangeiros', type: 'number', showWhen: (d) => d.purpose !== 'simulate_only', }, { key: 'exports_value', label: 'Exportações estimadas', type: 'number', }, { key: 'import_substitution_value', label: 'Substituição de importações estimada', type: 'number', }, { key: 'skills_needed', label: 'Qualificações/competências necessárias', type: 'textarea', }, { key: 'project_milestones', label: 'Marcos, prazo previsto e estado (planeado ou realizado)', type: 'textarea', showWhen: (d) => d.journey_type === 'small_business' && d.purpose === 'simulate_only', }, { key: 'local_content_percentage', label: 'Conteúdo local (%)', type: 'number', }, { key: 'environmental_category', label: 'Categoria ambiental', options: 'environmental_category', }, { key: 'community_benefits', label: 'Benefícios comunitários', type: 'textarea', }, ], tax: [ { key: 'fiscal_currency', label: 'Moeda da operação fiscal (sem conversão automática)', options: 'currency', showWhen: (d) => d.fiscal_component === 'iva' && d.fiscal_scenario_mode !== 'custom_planning', }, { key: 'fiscal_period', label: 'Ano do período fiscal a simular', options: 'fiscal_period', required: true, }, { key: 'fiscal_periodicity', label: 'Periodicidade/período de referência', options: 'fiscal_periodicity', required: true, }, { key: 'fiscal_month', label: 'Mês do período fiscal', options: 'fiscal_month', required: true, showWhen: (d) => d.fiscal_scenario_mode === 'approved_parameters' && d.fiscal_periodicity === 'monthly', }, { key: 'fiscal_quarter', label: 'Trimestre do período fiscal', options: 'fiscal_quarter', required: true, showWhen: (d) => d.fiscal_scenario_mode === 'approved_parameters' && d.fiscal_periodicity === 'quarterly', }, { key: 'fiscal_reference_date', label: 'Data da operação fiscal', type: 'date', required: true, showWhen: (d) => d.fiscal_scenario_mode === 'approved_parameters' && d.fiscal_periodicity === 'one_off', }, { key: 'fiscal_component', label: 'Componente que pretende explorar', options: 'fiscal_component', required: true, }, { key: 'irps_salary_view', label: 'Que resultado de remuneração pretende explorar?', options: 'irps_salary_view', required: true, showWhen: (d) => d.fiscal_component === 'irps' && d.fiscal_income_category === 'employment', }, { key: 'fiscal_income_category', label: 'Categoria de rendimento', options: 'fiscal_income_category', required: true, showWhen: (d) => ['irps', 'irpc', 'withholding'].includes( String(d.fiscal_component), ), }, { key: 'fiscal_residency_assessment', label: 'Residência fiscal indicada pelo utilizador', options: 'fiscal_residency_assessment', required: true, showWhen: (d) => ['irps', 'irpc', 'withholding', 'iva'].includes( String(d.fiscal_component), ), }, { key: 'fiscal_residency_country', label: 'País que indica como residência fiscal (opcional; não é verificação)', options: 'country', showWhen: (d) => ['irps', 'irpc', 'withholding', 'iva'].includes( String(d.fiscal_component), ), }, { key: 'fiscal_operation', label: 'Tipo de operação', options: 'fiscal_operation', required: true, showWhen: (d) => ['iva', 'irpc', 'withholding', 'customs_ice'].includes( String(d.fiscal_component), ), }, { key: 'fiscal_activity', label: 'Actividade económica (descrição exploratória)', required: true, showWhen: (d) => ['ispc', 'irpc'].includes(String(d.fiscal_component)), }, { key: 'fiscal_establishments', label: 'Número de estabelecimentos envolvidos', type: 'number', required: true, showWhen: (d) => ['ispc', 'iva', 'irpc'].includes(String(d.fiscal_component)), }, { key: 'fiscal_documentation', label: 'Estado da documentação de despesas', options: 'fiscal_documentation', required: true, showWhen: (d) => ['iva', 'irpc', 'irps'].includes(String(d.fiscal_component)), }, { key: 'fiscal_customs_code', label: 'Código pautal (necessário para classificação aduaneira)', required: true, showWhen: (d) => d.fiscal_component === 'customs_ice', }, { key: 'fiscal_vat_refund_state', label: 'Estado do crédito/pedido de reembolso de IVA', options: 'fiscal_refund_state', required: true, showWhen: (d) => d.fiscal_component === 'iva' && d.fiscal_operation === 'refund', }, { key: 'fiscal_scenario_mode', label: 'Modo de exploração fiscal', options: 'fiscal_scenario_mode', required: true, }, { key: 'base', label: 'Base hipotética para planeamento', type: 'number', showWhen: (d) => d.fiscal_scenario_mode === 'custom_planning', }, { key: 'rate', label: 'Taxa hipotética introduzida por si (%)', type: 'number', showWhen: (d) => d.fiscal_scenario_mode === 'custom_planning', }, { key: 'reduction', label: 'Redução hipotética introduzida por si (%)', type: 'number', showWhen: (d) => d.fiscal_scenario_mode === 'custom_planning', }, { key: 'sales_taxable', label: 'Vendas tributadas', type: 'number', showWhen: (d) => d.purpose !== 'simulate_only', }, { key: 'sales_exempt', label: 'Vendas isentas', type: 'number', showWhen: (d) => d.purpose !== 'simulate_only', }, { key: 'sales_zero_rate', label: 'Vendas à taxa zero', type: 'number', showWhen: (d) => d.purpose !== 'simulate_only', }, { key: 'exports', label: 'Exportações', type: 'number', showWhen: (d) => d.purpose !== 'simulate_only', }, { key: 'deductible_costs', label: 'Custos dedutíveis', type: 'number', showWhen: (d) => d.purpose !== 'simulate_only', }, { key: 'non_deductible_costs', label: 'Custos não dedutíveis', type: 'number', showWhen: (d) => d.purpose !== 'simulate_only', }, { key: 'carried_losses', label: 'Prejuízos transitados', type: 'number', showWhen: (d) => d.purpose !== 'simulate_only', }, { key: 'payroll', label: 'Dados de remunerações (estrutura prevista)', type: 'textarea', showWhen: (d) => d.purpose !== 'simulate_only', }, { key: 'outbound_payments', label: 'Pagamentos ao exterior (estrutura prevista)', type: 'textarea', showWhen: (d) => d.purpose !== 'simulate_only', }, { key: 'imports', label: 'Importações (estrutura prevista)', type: 'textarea', showWhen: (d) => d.purpose !== 'simulate_only', }, { key: 'claimed_benefits', label: 'Benefícios alegados para validação', type: 'textarea', showWhen: (d) => d.purpose !== 'simulate_only', }, { key: 'reference_date', label: 'Data de referência', type: 'date', required: true, showWhen: (d) => d.purpose !== 'simulate_only', }, ], opportunity: [ { key: 'offer_or_demand', label: 'Oferta ou procura', options: 'offer_or_demand', showWhen: (d) => d.manifestation_type === 'business' || (d.journey_type === 'small_business' && d.purpose === 'simulate_only'), }, { key: 'opportunity_type', label: 'Necessidade de negócio', options: 'opportunity_type', showWhen: (d) => d.manifestation_type === 'business' || (d.journey_type === 'small_business' && d.purpose === 'simulate_only'), }, { key: 'opportunity_type_other', label: 'Especifique a necessidade', required: (d) => d.purpose !== 'simulate_only', showWhen: (d) => (d.manifestation_type === 'business' || (d.journey_type === 'small_business' && d.purpose === 'simulate_only')) && d.opportunity_type === 'other', }, { key: 'opportunity_description', label: 'Descrição da necessidade', type: 'textarea', showWhen: (d) => d.manifestation_type === 'business' || (d.journey_type === 'small_business' && d.purpose === 'simulate_only'), }, { key: 'opportunity_value', label: 'Valor da oportunidade', type: 'number', showWhen: (d) => d.manifestation_type === 'business' || (d.journey_type === 'small_business' && d.purpose === 'simulate_only'), }, { key: 'opportunity_currency', label: 'Moeda da oportunidade', options: 'currency', showWhen: (d) => (d.manifestation_type === 'business' || (d.journey_type === 'small_business' && d.purpose === 'simulate_only')) && Boolean(d.opportunity_value), }, { key: 'opportunity_conditions', label: 'Condições da oportunidade', type: 'textarea', showWhen: (d) => d.manifestation_type === 'business', }, { key: 'opportunity_deadline', label: 'Prazo da oportunidade', type: 'date', showWhen: (d) => d.manifestation_type === 'business', }, { key: 'confidentiality', label: 'Confidencialidade', options: 'confidentiality', showWhen: (d) => d.manifestation_type === 'business', }, { key: 'publish_requested', label: 'Pretende pedir publicação?', options: 'preferred_publication', showWhen: (d) => d.manifestation_type === 'business', }, ], documents: [ { key: 'required_documents', label: 'Documentos aplicáveis a preparar', options: 'document_types', multiple: true, showWhen: (d) => d.purpose !== 'simulate_only', }, ], procedures: [ { key: 'required_documents', label: 'Documentos de suporte que já possui ou pretende preparar', options: 'document_types', multiple: true, showWhen: (d) => d.journey_type === 'investor' && d.purpose === 'simulate_only', }, ], review: [ { key: 'terms_accepted', label: 'Aceito os termos de utilização', options: 'yes_no', required: true, showWhen: (d) => d.purpose !== 'simulate_only', }, { key: 'truth_declaration', label: 'Declaro que as informações são verdadeiras', options: 'yes_no', required: true, showWhen: (d) => d.purpose !== 'simulate_only', }, { key: 'reuse_simulation', label: 'Autorizo reutilizar os dados da simulação', options: 'yes_no', required: true, showWhen: (d) => d.purpose !== 'simulate_only', }, { key: 'contact_authorisation', label: 'Autorizo contacto para acompanhamento', options: 'yes_no', required: true, showWhen: (d) => d.purpose !== 'simulate_only', }, { key: 'publication_authorisation', label: 'Autorizo a publicação solicitada', options: 'yes_no', required: true, showWhen: (d) => d.publish_requested === 'yes', }, { key: 'public_fields_confirmation', label: 'Confirmo os campos propostos para publicação', options: 'yes_no', required: true, showWhen: (d) => d.publish_requested === 'yes', }, { key: 'validation_acknowledged', label: 'Confirmo que li os avisos de validação', options: 'yes_no', required: true, showWhen: (d) => d.purpose !== 'simulate_only', }, { key: 'final_declaration_name', label: 'Nome para a declaração final', required: true, showWhen: (d) => d.purpose !== 'simulate_only', }, ], }; simulationOptions.yes_no = ['yes', 'no']; simulationOptions.infrastructure = [ 'energia', 'água', 'estrada', 'porto', 'ferrovia', 'telecomunicações', 'armazenamento', 'customers', 'raw_materials', 'workers', 'facilities', 'costs', 'legal_requirements', ]; export function replaceSimulationValue( draft: SimulationDraft, key: string, value: string | string[], ): SimulationDraft { const next = { ...draft, [key]: value }; if ( key === 'journey_type' && ['small_business', 'investor'].includes(String(value)) ) { next.purpose = 'simulate_only'; } const dependencies: Record = { province_id: ['district_id', 'admin_post_id', 'locality_id'], district_id: ['admin_post_id', 'locality_id'], admin_post_id: ['locality_id'], sector_id: ['activity_id'], site_known: ['latitude', 'longitude'], project_type: ['project_type_other'], opportunity_type: ['opportunity_type_other'], foreign_debt: ['funds_origin_countries'], fiscal_scenario_mode: ['base', 'rate', 'reduction'], fiscal_component: [ 'fiscal_income_category', 'irps_salary_view', 'fiscal_residency_assessment', 'fiscal_residency_country', 'fiscal_operation', 'fiscal_activity', 'fiscal_establishments', 'fiscal_documentation', 'fiscal_customs_code', 'fiscal_vat_refund_state', ], fiscal_operation: ['fiscal_vat_refund_state'], fiscal_income_category: ['irps_salary_view'], }; const clearChildren = key === 'foreign_debt' && Number(value) > 0 && Number(draft[key] ?? 0) > 0 ? [] : (dependencies[key] ?? []); for (const child of clearChildren) { delete next[child]; delete next[`${child}_label`]; } if (key === 'manifestation_type' && value !== 'business') { for (const field of simulationFieldGroups.opportunity) { delete next[field.key]; delete next[`${field.key}_label`]; } } return next; } const privateDraftKeys = new Set([ 'person_type', 'investor_origin', 'tax_residency_country', 'fiscal_residency_country', 'entity_stage', 'legal_form', 'nuit', 'investor_profile_id', 'profile_confirmation', 'legal_name', 'commercial_name', 'registration_number', 'representative_name', 'representative_capacity', 'email', 'phone', 'address', 'contact_country', 'contact_province', 'contact_district', 'preferred_channel', ]); export function getAutosaveSnapshot(draft: SimulationDraft): SimulationDraft { return Object.fromEntries( Object.entries(draft).filter(([key]) => { const field = key.replace(/_label$/, ''); return !privateDraftKeys.has(field); }), ); } export function partitionSimulationDraft( draft: SimulationDraft, includePersonal = true, ): { personal_snapshot: Record; economic_snapshot: Record; } { const personal_snapshot: Record = {}; const economic_snapshot: Record = {}; const personal_labels: Record = {}; const economic_labels: Record = {}; for (const [key, value] of Object.entries(draft)) { if ( !includePersonal && privateDraftKeys.has(key.replace(/_label$/, '')) ) continue; if (key.endsWith('_label')) { const target = privateDraftKeys.has(key.slice(0, -6)) ? personal_labels : economic_labels; target[key.slice(0, -6)] = value; continue; } (privateDraftKeys.has(key) ? personal_snapshot : economic_snapshot)[ key ] = value; } if (Object.keys(personal_labels).length) personal_snapshot.selection_labels = personal_labels; if (Object.keys(economic_labels).length) economic_snapshot.selection_labels = economic_labels; return { personal_snapshot, economic_snapshot }; } export function restoreSimulationDraft( economic: Record, personal: Record, ): SimulationDraft { const draft = { ...economic, ...personal } as Record; if (draft.project_questionnaire_version === undefined) { draft.project_questionnaire_version = 'legacy-unversioned'; } const labels = { ...(economic.selection_labels as Record | undefined), ...(personal.selection_labels as Record | undefined), }; delete draft.selection_labels; delete draft.personal_snapshot; delete draft.simulation_sources; if (labels && typeof labels === 'object') { for (const [key, value] of Object.entries(labels)) { if ( typeof value === 'string' || (Array.isArray(value) && value.every((item) => typeof item === 'string')) ) { draft[`${key}_label`] = Array.isArray(draft[key]) ? value : Array.isArray(value) ? value[0] : value; } } } return inheritSimulatorFields(draft as SimulationDraft); } export function simulationDraftFingerprint(draft: SimulationDraft): string { const sort = (value: unknown): unknown => { if (Array.isArray(value)) return value.map(sort); if (value && typeof value === 'object') return Object.fromEntries( Object.entries(value) .sort(([a], [b]) => a.localeCompare(b)) .map(([key, entry]) => [key, sort(entry)]), ); return value; }; return JSON.stringify(sort(partitionSimulationDraft(draft))); } export function validateSimulationDraft( draft: SimulationDraft, sectionId: string, ): Record { // The separate arithmetic validator checks its three inputs. Fiscal // eligibility facts are not inputs of this planning-only calculation. if (sectionId === 'tax' && draft.fiscal_scenario_mode === 'custom_planning') return {}; const errors: Record = {}; for (const field of simulationFieldGroups[sectionId] ?? []) { if (field.showWhen && !field.showWhen(draft)) continue; if ( sectionId === 'tax' && draft.fiscal_scenario_mode === 'custom_planning' && ['base', 'rate', 'reduction'].includes(field.key) ) continue; const value = draft[field.key]; const empty = value === undefined || value === '' || (Array.isArray(value) && value.length === 0); const required = typeof field.required === 'function' ? field.required(draft) : Boolean(field.required); if ( required && empty && !(sectionId === 'tax' && isInvestorProjectJourney(draft)) ) { errors[field.key] = 'Este campo é obrigatório.'; } if (field.type === 'number' && value !== undefined && value !== '') { const number = Number(value); if (!Number.isFinite(number) || number < 0) errors[field.key] = 'Introduza um número igual ou superior a zero.'; } if ( ['rate', 'reduction', 'local_content_percentage'].includes( field.key, ) && value !== undefined && value !== '' ) { if (Number(value) > 100) errors[field.key] = 'O valor não pode exceder 100.'; } if ( field.key === 'email' && typeof value === 'string' && value && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) ) errors[field.key] = 'Introduza um endereço de correio electrónico válido.'; } if ( sectionId === 'tax' && draft.fiscal_scenario_mode === 'approved_parameters' && draft.fiscal_periodicity === 'one_off' && !getFiscalPeriodRange(draft) ) { errors.fiscal_reference_date = 'Introduza uma data válida da operação no ano fiscal seleccionado. Não foi usada uma data alternativa.'; } if ( sectionId === 'financial' && [ 'own_capital', 'domestic_debt', 'foreign_debt', 'grants', 'total_investment', ].every((key) => draft[key] !== undefined && draft[key] !== '') ) { const total = [ 'own_capital', 'domestic_debt', 'foreign_debt', 'grants', ].reduce((sum, key) => sum + Number(draft[key]), 0); if (Math.abs(total - Number(draft.total_investment)) > 0.01) errors.total_investment = 'A soma do capital, dívida e subvenções deve corresponder ao investimento total.'; } return errors; } export function validateCustomFiscalPlanning( draft: SimulationDraft, ): Record { if (draft.fiscal_scenario_mode !== 'custom_planning') return {}; const errors: Record = {}; for (const key of ['base', 'rate', 'reduction']) { const value = draft[key]; if ( value === undefined || value === '' || (typeof value === 'string' && value.trim() === '') ) { errors[key] = 'Este pressuposto é necessário para executar este cenário.'; } else if (!Number.isFinite(Number(value)) || Number(value) < 0) { errors[key] = 'Introduza um número igual ou superior a zero.'; } else if (['rate', 'reduction'].includes(key) && Number(value) > 100) { errors[key] = 'O valor não pode exceder 100.'; } } return errors; } export type ArithmeticSnapshotRead = { integrity: 'versioned' | 'legacy' | 'invalid'; value: string | null; context: Record | null; explanation: string | null; }; /** Reads an internally consistent arithmetic snapshot without inferring tax values. */ export function readArithmeticSnapshot( snapshot: unknown, ): ArithmeticSnapshotRead { if (!snapshot || typeof snapshot !== 'object' || Array.isArray(snapshot)) { return { integrity: 'invalid', value: null, context: null, explanation: null, }; } const result = snapshot as Record; const value = result.indicative_total; if ( ['fiscal_rule_qa_preview', 'fiscal_rule_published'].includes( String(result.scenario_kind), ) ) { const presentation = result.presentation as | Record | undefined; if ( isApiSimulationResultSnapshotV1(result) && fiscalQaRows(result).length > 0 && result.result.value === value && presentation?.value === value && presentation?.rounding_applied === false && result.context.currency === 'MZN' && result.result.unit === 'MZN' && ((result.result.status === 'calculated' && typeof value === 'string' && /^\d+(?:\.\d+)?$/.test(value)) || (result.result.status === 'indeterminate' && value === null && result.result.missing_inputs.length > 0)) ) { return { integrity: 'versioned', value: value as string | null, context: result.context as unknown as Record, explanation: result.result.explanation, }; } return { integrity: 'invalid', value: null, context: null, explanation: null, }; } if (typeof value !== 'string' || !/^(?:\d+)(?:\.\d+)?$/.test(value)) { return { integrity: 'invalid', value: null, context: null, explanation: null, }; } if (result.contract_version === undefined) { return { integrity: 'legacy', value, context: null, explanation: null }; } if ( result.contract_version !== 1 || !isApiSimulationResultSnapshotV1(result) ) { return { integrity: 'invalid', value: null, context: null, explanation: null, }; } const context = result.context; const output = result.result; if ( !context || typeof context !== 'object' || Array.isArray(context) || !output || typeof output !== 'object' || Array.isArray(output) ) { return { integrity: 'invalid', value: null, context: null, explanation: null, }; } const resultContext = context as Record; const resultOutput = output as Record; if ( resultOutput.status !== 'calculated' || resultOutput.value !== value || resultOutput.unit !== null || typeof resultOutput.explanation !== 'string' || !resultOutput.explanation.trim() || !Array.isArray(resultOutput.missing_inputs) || resultOutput.missing_inputs.length !== 0 || !Array.isArray(resultOutput.source_ids) || resultOutput.source_ids.length !== 0 || typeof resultContext.reference_date !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(resultContext.reference_date) || typeof resultContext.scenario_id !== 'string' || !Number.isSafeInteger(resultContext.scenario_version) || Number(resultContext.scenario_version) < 1 || typeof resultContext.engine_version !== 'string' || resultContext.currency !== null || resultContext.currency_fraction_digits !== null || resultContext.ruleset_version !== null ) { return { integrity: 'invalid', value: null, context: null, explanation: null, }; } return { integrity: 'versioned', value, context: resultContext, explanation: resultOutput.explanation, }; } function canonicalJson(value: unknown): string { if (Array.isArray(value)) { return `[${value.map(canonicalJson).join(',')}]`; } if (value && typeof value === 'object') { const record = value as Record; return `{${Object.keys(record) .sort() .map( (key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`, ) .join(',')}}`; } return JSON.stringify(value) ?? 'null'; } export type ArithmeticReportReference = { id: string; version_no: number; report_sha256: string; created_at: string | null; }; /** Returns a saved report reference only when it matches the current immutable snapshot. */ export function readVersionedArithmeticReportReference( simulation: Record | null | undefined, ): ArithmeticReportReference | null { if ( simulation?.status !== 'completed' || !simulation.result_snapshot || typeof simulation.result_snapshot !== 'object' || Array.isArray(simulation.result_snapshot) ) { return null; } const snapshot = simulation.result_snapshot as Record; if (snapshot.contract_version !== 1) return null; const context = snapshot.context; if (!context || typeof context !== 'object' || Array.isArray(context)) return null; const scenarioVersion = (context as Record) .scenario_version; if (!Number.isSafeInteger(scenarioVersion) || Number(scenarioVersion) < 1) return null; const reports = Array.isArray(simulation.reports) ? (simulation.reports.filter( (report) => report && typeof report === 'object' && !Array.isArray(report), ) as Record[]) : []; reports.sort((left, right) => { const leftTime = typeof left.created_at === 'string' ? left.created_at : ''; const rightTime = typeof right.created_at === 'string' ? right.created_at : ''; return rightTime.localeCompare(leftTime); }); const report = reports.find( (candidate) => candidate.status === 'ready' && candidate.version_no === scenarioVersion && typeof candidate.id === 'string' && candidate.report_snapshot && typeof candidate.report_snapshot === 'object' && !Array.isArray(candidate.report_snapshot) && canonicalJson(candidate.report_snapshot) === canonicalJson(snapshot) && typeof candidate.report_sha256 === 'string' && /^[a-f0-9]{64}$/i.test(candidate.report_sha256), ); if (!report) return null; return { id: report.id as string, version_no: scenarioVersion as number, report_sha256: report.report_sha256 as string, created_at: typeof report.created_at === 'string' ? report.created_at : null, }; } /** Local export of the generic arithmetic result; never includes personal/profile fields. */ export function exportCompletedArithmeticCsv( simulation: Record, exportedAt = new Date().toISOString(), ): string { if ( simulation.status !== 'completed' || !simulation.result_snapshot || typeof simulation.result_snapshot !== 'object' ) { throw new Error( 'Só é possível exportar uma simulação concluída com relatório disponível.', ); } const result = simulation.result_snapshot as Record; if ( ['fiscal_rule_qa_preview', 'fiscal_rule_published'].includes( String(result.scenario_kind), ) ) { const rows = fiscalQaRows(result); const report = readVersionedArithmeticReportReference(simulation); if (!rows.length || !report) throw new Error( 'A pré-visualização fiscal não corresponde ao relatório preservado.', ); return `\uFEFF${[ ...rows, ['ID do relatório', report.id], ['Hash do relatório', report.report_sha256], ['Data de exportação', exportedAt], ] .map((field) => field.map(csvCell).join(',')) .join('\r\n')}`; } const decimal = (value: unknown, label: string) => { if (typeof value !== 'string' || !/^(?:\d+)(?:\.\d+)?$/.test(value)) throw new Error( `O campo ${label} não está disponível num formato seguro.`, ); return value; }; if ( result.contract_version !== undefined && result.contract_version !== 1 ) { throw new Error('A versão do contrato do resultado não é suportada.'); } let versionedFields: Array<[string, unknown]> = []; if (result.contract_version === 1) { const context = result.context; const output = result.result; if ( !context || typeof context !== 'object' || !output || typeof output !== 'object' ) { throw new Error( 'O snapshot versionado do resultado está incompleto.', ); } const resultContext = context as Record; const resultOutput = output as Record; const exactValue = decimal(resultOutput.value, 'resultado versionado'); if ( resultOutput.status !== 'calculated' || exactValue !== decimal(result.indicative_total, 'resultado') || result.legal_interpretation !== false || typeof resultContext.reference_date !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(resultContext.reference_date) || typeof resultContext.scenario_id !== 'string' || !Number.isSafeInteger(resultContext.scenario_version) || Number(resultContext.scenario_version) < 1 || typeof resultContext.engine_version !== 'string' || resultContext.currency !== null || resultContext.ruleset_version !== null ) { throw new Error( 'O envelope do resultado não reconcilia com o snapshot aritmético.', ); } versionedFields = [ ['Versão do contrato', '1'], ['Data de referência', resultContext.reference_date], ['ID do cenário', resultContext.scenario_id], ['Versão do motor', resultContext.engine_version], ['Estado do resultado', resultOutput.status], ['Explicação do resultado', resultOutput.explanation], ]; } const reports = Array.isArray(simulation.reports) ? (simulation.reports.filter( (report) => report && typeof report === 'object' && !Array.isArray(report), ) as Record[]) : []; const readyReports = [...reports] .filter((report) => report.status === 'ready') .sort((left, right) => { const leftTime = typeof left.created_at === 'string' ? left.created_at : ''; const rightTime = typeof right.created_at === 'string' ? right.created_at : ''; return rightTime.localeCompare(leftTime); }); let latestReport = readyReports[0]; if (result.contract_version === 1) { const reference = readVersionedArithmeticReportReference(simulation); if (!reference) { throw new Error( 'Relatorio imutavel nao corresponde a versao atual do resultado.', ); } latestReport = reference; } const version = Number.isSafeInteger(simulation.version_no) ? String(simulation.version_no) : 'não identificada'; const fields: Array<[string, unknown]> = [ ['Relatório', 'Simulação aritmética indicativa'], ...versionedFields, ['Data de exportação', exportedAt], [ 'Data do resultado', typeof simulation.updated_at === 'string' ? simulation.updated_at : 'não disponível', ], ['Período', 'cenário aritmético pontual; sem período fiscal atribuído'], [ 'ID da execução', typeof simulation.id === 'string' ? simulation.id : 'não disponível', ], ['Versão da execução', version], [ 'ID do relatório', typeof latestReport?.id === 'string' ? latestReport.id : 'não disponível', ], [ 'Versão do relatório/cenário', Number.isSafeInteger(latestReport?.version_no) ? String(latestReport?.version_no) : 'não identificada', ], [ 'Hash SHA-256 do relatório', typeof latestReport?.report_sha256 === 'string' && /^[a-f0-9]{64}$/i.test(latestReport.report_sha256) ? latestReport.report_sha256 : 'não disponível', ], [ 'Fórmula', typeof result.formula_version === 'string' ? result.formula_version : 'não identificada', ], ['Versão de regras', 'não aplicável; motor aritmético genérico'], [ 'Expressão aplicada', 'base × taxa_percentual ÷ 100 × (1 − redução_percentual ÷ 100)', ], ['Base introduzida', decimal(result.base, 'base')], ['Taxa percentual introduzida', decimal(result.rate_percent, 'taxa')], [ 'Redução hipotética percentual', decimal(result.reduction_percent, 'redução'), ], [ 'Resultado aritmético indicativo', decimal(result.indicative_total, 'resultado'), ], [ 'Moeda', typeof result.currency === 'string' ? result.currency : 'não fornecida', ], [ 'Fontes legais', 'nenhuma associada; parâmetros fornecidos pelo utilizador', ], ['IDs de fontes', 'nenhum'], [ 'Arredondamento fiscal', 'não aplicado; sem regra de arredondamento fiscal', ], [ 'Interpretação legal', result.legal_interpretation === false ? 'não' : 'não confirmada', ], [ 'Limitações', 'Aritmética indicativa; sem validação fiscal, fundamento legal, arredondamento fiscal ou aconselhamento.', ], ]; return `\uFEFF${fields.map((field) => field.map(csvCell).join(',')).join('\r\n')}`; }