export type SimulationDraft = Record; 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; export const simulationOptions: Record = { 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"], tax_calculation: ["yes", "no"], 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"], }; export const simulationFieldGroups: Record boolean }[]> = { purpose: [ { 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: "tax_residency_country", label: "País de residência fiscal", options: "country", 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", required: true, showWhen: d => d.purpose !== "simulate_only" }, { key: "project_summary", label: "Resumo executivo", type: "textarea", required: true, showWhen: d => d.purpose !== "simulate_only" }, { key: "project_type", label: "Tipo de projecto", options: "project_type", required: true, showWhen: d => d.purpose !== "simulate_only" }, { 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: true, showWhen: d => d.purpose !== "simulate_only" }, { key: "activity_id", label: "Actividade económica", options: "activity", required: true, showWhen: d => d.purpose !== "simulate_only" }, { key: "products_services", label: "Produtos e serviços", type: "textarea", showWhen: d => d.purpose !== "simulate_only" }, { key: "maturity_stage", label: "Fase de maturidade", options: "maturity_stage", required: true, showWhen: d => d.purpose !== "simulate_only" }, { key: "expected_start_date", label: "Data prevista de início", type: "date", showWhen: d => d.purpose !== "simulate_only" }, { key: "project_duration_years", label: "Duração prevista (anos)", type: "number", showWhen: d => d.purpose !== "simulate_only" }, { key: "existing_project_id", label: "Referência do projecto existente", showWhen: d => d.project_type === "expansion" }, ], location: [ { key: "province_id", label: "Província", options: "province", required: true, showWhen: d => d.purpose !== "simulate_only" }, { key: "district_id", label: "Distrito", options: "district", required: true, showWhen: d => d.purpose !== "simulate_only" }, { key: "admin_post_id", label: "Posto administrativo", options: "admin_post", showWhen: d => d.purpose !== "simulate_only" }, { key: "locality_id", label: "Localidade", options: "locality", showWhen: d => d.purpose !== "simulate_only" }, { key: "site_known", label: "Local conhecido?", options: "yes_no", required: true, showWhen: d => d.purpose !== "simulate_only" }, { key: "land_status", label: "Situação do terreno", options: "land_status", showWhen: d => d.purpose !== "simulate_only" }, { key: "area_required", label: "Área necessária", type: "number", showWhen: d => d.purpose !== "simulate_only" }, { key: "area_unit", label: "Unidade da área", options: "area_unit", showWhen: d => d.purpose !== "simulate_only" }, { 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" }, { key: "infrastructure_needs", label: "Necessidades de infra-estrutura", options: "infrastructure", multiple: true, showWhen: d => d.purpose !== "simulate_only" }, ], financial: [ { key: "currency", label: "Moeda", options: "currency", required: true, showWhen: d => d.purpose !== "simulate_only" }, { key: "total_investment", label: "Investimento total", type: "number", required: true, showWhen: d => d.purpose !== "simulate_only" }, { key: "own_capital", label: "Capital próprio", type: "number", required: true, showWhen: d => d.purpose !== "simulate_only" }, { key: "domestic_debt", label: "Financiamento interno", type: "number", required: true, showWhen: d => d.purpose !== "simulate_only" }, { key: "foreign_debt", label: "Financiamento externo", type: "number", required: true, showWhen: d => d.purpose !== "simulate_only" }, { key: "grants", label: "Subvenções", type: "number", required: true, showWhen: d => d.purpose !== "simulate_only" }, { key: "secured_amount", label: "Montante mobilizado", type: "number", required: true, showWhen: d => d.purpose !== "simulate_only" }, { key: "sought_amount", label: "Montante procurado", type: "number", showWhen: d => d.purpose !== "simulate_only" }, { key: "annual_revenue", label: "Receitas anuais estimadas", type: "number", showWhen: d => d.purpose !== "simulate_only" }, { key: "annual_operating_costs", label: "Custos operacionais anuais", type: "number", showWhen: d => d.purpose !== "simulate_only" }, { 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: "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: "tax_calculation", label: "Pretende calcular um cenário aritmético?", options: "tax_calculation", required: true }, { key: "base", label: "Base para o cenário", type: "number", required: true, showWhen: d => d.tax_calculation === "yes" }, { key: "rate", label: "Taxa hipotética do cenário (%)", type: "number", required: true, showWhen: d => d.tax_calculation === "yes" }, { key: "reduction", label: "Redução hipotética (%)", type: "number", required: true, showWhen: d => d.tax_calculation === "yes" }, { 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" }, { key: "opportunity_type", label: "Necessidade de negócio", options: "opportunity_type", showWhen: d => d.manifestation_type === "business" }, { key: "opportunity_type_other", label: "Especifique a necessidade", required: true, showWhen: d => d.manifestation_type === "business" && d.opportunity_type === "other" }, { key: "opportunity_description", label: "Descrição da necessidade", type: "textarea", showWhen: d => d.manifestation_type === "business" }, { key: "opportunity_value", label: "Valor da oportunidade", type: "number", showWhen: d => d.manifestation_type === "business" }, { key: "opportunity_currency", label: "Moeda da oportunidade", options: "currency", showWhen: d => d.manifestation_type === "business" && 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" }, ], 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"]; export function replaceSimulationValue(draft: SimulationDraft, key: string, value: string | string[]): SimulationDraft { const next = { ...draft, [key]: value }; 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"], tax_calculation: ["base", "rate", "reduction"], }; 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 === "purpose" && value === "simulate_only") { for (const fields of Object.values(simulationFieldGroups)) for (const field of fields) { if (["purpose", "preferred_language"].includes(field.key)) continue; delete next[field.key]; delete next[`${field.key}_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", "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): { 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 (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; const labels = { ...(economic.selection_labels as Record | undefined), ...(personal.selection_labels as Record | undefined) }; delete draft.selection_labels; delete draft.personal_snapshot; 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 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 { const errors: Record = {}; for (const field of simulationFieldGroups[sectionId] ?? []) { if (field.showWhen && !field.showWhen(draft)) continue; const value = draft[field.key]; const empty = value === undefined || value === "" || (Array.isArray(value) && value.length === 0); if (field.required && empty) 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 === "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; }