Files
Investidor-V2/app/Services/PrivateInvestmentPreparation.php
T

220 lines
18 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Services;
use Brick\Math\BigDecimal;
/** Adapter of the dossier's declared context to preparation of a private-investment request. No tax or entitlement decision. */
final class PrivateInvestmentPreparation
{
public const VERSION = 'private-investment-preparation-2026-v1';
public const SOURCE = 'https://pt.apiex.gov.mz/wp-content/uploads/2025/02/LEGISLACAO-DE-INVESTIMENTOS-EM-MOCAMBIQUE.pdf';
public const HASH = 'ba1e9eaabfec5d0c4cb2d9f2444a6860f7a90f222034764197829be402d408ed';
public const FIELDS = ['investment_form', 'investment_preparation', 'investment_activity_scope', 'implementation_mode', 'capital_origin', 'site_tenure', 'project_concession', 'significant_implications', 'land_extension', 'forest_concession', 'forest_concession_hectares', 'total_investment', 'area_required', 'area_unit'];
public const DOCUMENTS = ['identity', 'registration', 'name_reservation', 'site_plan', 'lease', 'duat', 'representation_license', 'feasibility', 'financial_capacity', 'foreign_capital'];
public function available(): bool
{
return (bool) config('investor-path.private_preparation', false);
}
/**
* @return array<array-key, mixed>
*/
public function questions(): array
{
$tri = ['unknown' => 'Por determinar', 'yes' => 'Sim', 'no' => 'Não'];
return [
['key' => 'investment_preparation', 'label' => 'Pretende preparar o pedido de garantias/incentivos ao investimento privado?', 'options' => $tri, 'source' => 'Lei 8/2023, artigo 2'],
['key' => 'investment_activity_scope', 'label' => 'Natureza efectiva da actividade do projecto', 'options' => ['unknown' => 'Por determinar', 'general' => 'Outra actividade económica', 'extractive' => 'Prospecção, pesquisa ou produção extractiva', 'processing' => 'Processamento industrial mineiro/petrolífero', 'public_nonprofit' => 'Investimento público ou exclusivamente social/sem fins lucrativos'], 'source' => 'Lei 8/2023, artigos 2 e 22'],
['key' => 'investment_form', 'label' => 'Tipo deste investimento', 'options' => ['unknown' => 'Por determinar', 'direct' => 'Directo', 'indirect' => 'Indirecto'], 'source' => 'Lei 8/2023, artigo 14(b); Regulamento, artigo 13', 'help' => 'O limiar monetário do artigo 13 aplica-se ao investimento directo. A forma indirecta não é convertida silenciosamente em directa.'],
['key' => 'implementation_mode', 'label' => 'Modalidade da empresa implementadora', 'options' => ['unknown' => 'Por determinar', 'domestic_entity' => 'Empresa constituída/em constituição em Moçambique', 'foreign_representation' => 'Representação comercial estrangeira'], 'source' => 'Regulamento 8/2024, artigo 9(2)'],
['key' => 'capital_origin', 'label' => 'Origem dos capitais/recursos deste investimento', 'options' => ['unknown' => 'Por determinar', 'national' => 'Nacionais', 'external' => 'Provenientes do exterior', 'mixed' => 'Mistos'], 'source' => 'Lei 8/2023, glossário; Regulamento, artigos 4–6'],
['key' => 'site_tenure', 'label' => 'Fundamento declarado da utilização das instalações', 'options' => ['unknown' => 'Por determinar', 'lease' => 'Arrendamento', 'duat' => 'DUAT'], 'source' => 'Regulamento, artigos 9(1)(d) e 14(1)(d)'],
['key' => 'project_concession', 'label' => 'O empreendimento é PPP ou concessão empresarial?', 'options' => $tri, 'source' => 'Lei 8/2023, artigo 22(1)(b)(ii)'],
['key' => 'significant_implications', 'label' => 'Existem previsíveis implicações económicas, ambientais, de segurança ou saúde pública que exijam autorização?', 'options' => $tri, 'source' => 'Lei 8/2023, artigo 22(1)(b)(i)', 'help' => 'Declare o enquadramento conhecido; o nome do sector não responde a esta pergunta. Sem fundamento suficiente, mantenha por determinar.'],
['key' => 'land_extension', 'label' => 'O projecto requer extensão de terra?', 'options' => $tri, 'source' => 'Lei 8/2023, artigo 22(1)(b)(iii)'],
['key' => 'forest_concession', 'label' => 'O projecto requer concessão florestal?', 'options' => $tri, 'source' => 'Lei 8/2023, artigo 22(1)(b)(iv)'],
['key' => 'forest_concession_hectares', 'label' => 'Área da concessão florestal (hectares)', 'type' => 'number', 'when' => ['field' => 'forest_concession', 'equals' => 'yes'], 'source' => 'Lei 8/2023, artigo 22(1)(b)(iv)'],
['key' => 'total_investment', 'label' => 'Valor total do investimento na moeda do projecto', 'type' => 'number', 'source' => 'Regulamento, artigo 13(2)'],
['key' => 'area_required', 'label' => 'Área de terra requerida', 'type' => 'number', 'when' => ['field' => 'land_extension', 'equals' => 'yes'], 'source' => 'Lei 8/2023, artigo 22(1)(b)(iii)'],
['key' => 'area_unit', 'label' => 'Unidade da área requerida', 'options' => ['ha' => 'Hectares', 'm2' => 'Metros quadrados'], 'when' => ['field' => 'land_extension', 'equals' => 'yes'], 'source' => 'Conversão de unidade exacta; não é benefício ou condição adicional'],
];
}
/**
* @param array<array-key, mixed> $f
*/
private function number(array $f, string $key): ?BigDecimal
{
$value = $f[$key] ?? '';
return is_scalar($value) && preg_match('/^\d+(?:\.\d+)?$/D', (string) $value) ? BigDecimal::of((string) $value) : null;
}
/** @param array<array-key, mixed> $fields
* @param list<string> $missing
*/
private function answer(array $fields, string $key, array &$missing): ?bool
{
$value = $fields[$key] ?? null;
if ($value === 'yes') {
return true;
}
if ($value === 'no') {
return false;
}
$missing[] = $key;
return null;
}
/** @param array<string, bool|null> $triggers
* @return list<string>
*/
private function triggerReasons(array $triggers): array
{
$reasons = [];
foreach (['amount' => 'Valor superior a 32 mil milhões MZN — R13(2), R21(2)(a)', 'project_concession' => 'PPP/concessão empresarial — Lei 8/2023, art. 22(1)(b)(ii), R21(2)(b)', 'significant_implications' => 'Implicações significativas declaradas — Lei 8/2023, art. 22(1)(b)(i), R21(1)', 'land_extension' => 'Extensão de terra igual/superior a 10 mil hectares — Lei 8/2023, art. 22(1)(b)(iii), R21(2)(c)', 'forest_concession' => 'Concessão florestal superior a 100 mil hectares — Lei 8/2023, art. 22(1)(b)(iv), R21(2)(d)'] as $condition => $description) {
if (($triggers[$condition] ?? null) === true) {
$reasons[] = $description.'.';
}
}
return $reasons;
}
/**
* @param array<array-key, mixed> $context
* @param array<array-key, mixed> $documents
* @return array<array-key, mixed>
*/
public function assess(array $context, array $documents = []): array
{
$f = $context['fields'] ?? [];
$missing = [];
$enabled = ($f['investment_preparation'] ?? '') === 'yes';
$date = $f['expected_start_date'] ?? '';
$period = preg_match('/^2026-\d{2}-\d{2}$/D', $date) === 1;
$scope = $f['investment_activity_scope'] ?? 'unknown';
$covered = in_array($scope, ['general', 'processing'], true);
$mode = 'indeterminate';
$reasons = [];
if ($enabled) {
if (! $date) {
$missing[] = 'expected_start_date';
}
if ($scope === 'unknown' || $scope === '') {
$missing[] = 'investment_activity_scope';
}
if (empty($f['currency'])) {
$missing[] = 'currency';
}
if ($covered && $period) {
$triggers = [];
if ($scope === 'processing') {
$triggers['processing'] = true;
$reasons[] = 'Processamento industrial mineiro/petrolífero: Lei 8/2023, artigo 22(1)(b)(v).';
}
$amount = $this->number($f, 'total_investment');
$triggers['amount'] = $amount && ($f['currency'] ?? '') === 'MZN' && ($f['investment_form'] ?? '') === 'direct' ? $amount->isGreaterThan('32000000000') : null;
if (! in_array($f['investment_form'] ?? '', ['direct', 'indirect'], true)) {
$missing[] = 'investment_form';
}
if (($f['investment_form'] ?? '') === 'indirect') {
$reasons[] = 'Investimento indirecto: a classificação pela dimensão e o encaminhamento monetário não são determinados por este lote. As restantes condições documentadas continuam avaliadas.';
}
if (! $amount) {
$missing[] = 'total_investment';
}
if (($f['currency'] ?? '') !== 'MZN') {
$reasons[] = 'A condição monetária não é determinada noutra moeda: não se fez conversão.';
}
foreach (['project_concession', 'significant_implications'] as $key) {
$triggers[$key] = $this->answer($f, $key, $missing);
}
$land = $this->answer($f, 'land_extension', $missing);
if ($land === true) {
$area = $this->number($f, 'area_required');
$unit = $f['area_unit'] ?? '';
if (! $area) {
$missing[] = 'area_required';
}if (! in_array($unit, ['ha', 'm2'], true)) {
$missing[] = 'area_unit';
}
$triggers['land_extension'] = $area && in_array($unit, ['ha', 'm2'], true) ? $area->isGreaterThanOrEqualTo($unit === 'ha' ? '10000' : '100000000') : null;
} else {
$triggers['land_extension'] = $land;
}
$forest = $this->answer($f, 'forest_concession', $missing);
if ($forest === true) {
$area = $this->number($f, 'forest_concession_hectares');
if (! $area) {
$missing[] = 'forest_concession_hectares';
}$triggers['forest_concession'] = $area ? $area->isGreaterThan('100000') : null;
} else {
$triggers['forest_concession'] = $forest;
}
$reasons = [...$reasons, ...$this->triggerReasons($triggers)];
$mode = in_array(true, $triggers, true) ? 'authorization' : (in_array(null, $triggers, true) ? 'indeterminate' : 'registration');
$reasons[] = $mode === 'authorization' ? 'Foi identificada uma condição de autorização; as restantes condições desconhecidas não foram presumidas negativas.' : ($mode === 'registration' ? 'As condições de autorização deste lote foram declaradas negativas e o montante não ultrapassa o limite.' : 'Falta resolver pelo menos uma condição de autorização.');
}
foreach (['implementation_mode', 'capital_origin', 'site_tenure', 'entity_stage'] as $key) {
if (empty($f[$key]) || $f[$key] === 'unknown') {
$missing[] = $key;
}
}
}
if ($mode === 'authorization' && in_array(true, array_intersect_key($triggers, array_flip(['amount', 'project_concession', 'land_extension', 'forest_concession'])), true)) {
$missing = array_values(array_diff($missing, ['investment_form', 'project_concession', 'significant_implications', 'land_extension', 'forest_concession', 'forest_concession_hectares', 'area_required', 'area_unit', 'total_investment']));
}
$base = $enabled && $covered && $period ? true : null;
if (! $enabled || in_array($scope, ['extractive', 'public_nonprofit'], true) || ($date && ! $period)) {
$base = false;
}
$rules = [
['identity', 'Identificação do investidor', $base, 'S06', 'R9(1)(a) / R14(1)(a)', 41, 'Identificação do proponente do pedido.'],
['registration', 'Certidão de registo comercial', $base === true ? (($f['entity_stage'] ?? '') === 'existing' ? true : (($f['entity_stage'] ?? '') === 'being_formed' ? false : null)) : $base, 'S05', 'R9(1)(b) / R14(1)(b)', 41, 'Alternativa para empresa já registada.'],
['name_reservation', 'Reserva da denominação social', $base === true ? (($f['entity_stage'] ?? '') === 'being_formed' ? true : (($f['entity_stage'] ?? '') === 'existing' ? false : null)) : $base, 'S05', 'R9(1)(b) / R14(1)(b)', 41, 'Alternativa para empresa em constituição.'],
['site_plan', 'Planta ou esboço do local', $base, 'S06', 'R9(1)(c) / R14(1)(c)', 42, 'Local de implantação identificado; não certifica disponibilidade.'],
];
foreach (['lease' => 'Contrato de arrendamento', 'duat' => 'Título DUAT'] as $key => $label) {
$rules[] = [$key, $label, $base === true ? (($f['site_tenure'] ?? 'unknown') === 'unknown' ? null : ($f['site_tenure'] ?? '') === $key) : $base, 'S06', 'R9(1)(d) / R14(1)(d)', 42, 'Alternativa correspondente à utilização declarada das instalações.'];
}
$rules[] = ['representation_license', 'Licença de representação comercial', $base === true ? (($f['implementation_mode'] ?? 'unknown') === 'unknown' ? null : ($f['implementation_mode'] ?? '') === 'foreign_representation') : $base, 'S06', 'R9(2)', 42, 'Só para a modalidade representação comercial estrangeira; não decorre da nacionalidade.'];
foreach (['feasibility' => 'Estudo de viabilidade técnica e económico-financeira', 'financial_capacity' => 'Comprovação da capacidade financeira'] as $key => $label) {
$rules[] = [$key, $label, $base === true ? ($mode === 'indeterminate' ? null : $mode === 'authorization') : $base, 'S02', 'R14(1)–(2)', 45, 'Integra a preparação do pedido de autorização.'];
}
$rules[] = ['foreign_capital', 'Prova de realização/registo do aporte externo', $base === true ? (($f['capital_origin'] ?? 'unknown') === 'unknown' ? null : in_array($f['capital_origin'] ?? '', ['external', 'mixed'], true)) : $base, 'S06', 'R5(1)–(5)', 39, 'Preparação específica do aporte externo; formalidades cambiais concretas não calculadas.'];
$links = $context['preparation_documents'] ?? [];
$requirements = [];
foreach ($rules as [$key,$label,$applies,$stage,$article,$page,$reason]) {
$doc = isset($links[$key]) ? collect($documents)->firstWhere('document_id', $links[$key]) : null;
$requirements[] = ['key' => $key, 'label' => $label, 'state' => $applies === false ? 'not_applicable' : ($applies === null ? 'indeterminate' : ($doc ? 'associated' : 'missing')), 'required' => $applies === true, 'document_id' => $links[$key] ?? null, 'document' => $doc, 'stage' => $stage, 'reason' => $reason, 'source' => ['reference' => $article, 'url' => self::SOURCE.'#page='.$page, 'sha256' => self::HASH, 'period' => '2026; Regulamento desde 07/03/2024'], 'legal_verification' => 'not_inferred'];
}
$authoritySource = ['reference' => 'R13(1)', 'url' => self::SOURCE.'#page=45'];
$zone = $context['selection']['park'] ?? null;
$amount = $this->number($f, 'total_investment');
$authority = 'Por determinar neste lote';
if ($base === true && $mode === 'registration' && $amount && ($f['currency'] ?? '') === 'MZN') {
$special = in_array($zone['fields']['investment_regime'] ?? '', ['zee', 'zfi'], true);
$authority = $special ? ($amount->isLessThanOrEqualTo('6500000000') ? 'APIEX — condicionado ao enquadramento ZEE/ZFI efectivamente reconhecido' : 'Ministro que superintende Finanças') : ($amount->isLessThanOrEqualTo('3500000000') ? 'Governador da Província' : ($amount->isLessThanOrEqualTo('6500000000') ? 'APIEX' : 'Ministro que superintende Finanças'));
}
if ($base === true && $mode === 'authorization') {
$council = array_intersect_key($triggers, array_flip(['amount', 'project_concession', 'land_extension', 'forest_concession']));
$authority = in_array(true, $council, true) ? 'Conselho de Ministros' : (in_array(null, $council, true) ? 'Autorização identificada; completar as condições em falta para distinguir a entidade decisória' : 'Ministro que superintende Finanças; eventual submissão ao Conselho de Ministros nos termos do R21(3)');
$authoritySource = ['reference' => 'R21(1)–(3)', 'url' => self::SOURCE.'#page=50'];
}
$labels = array_column($this->questions(), 'label', 'key') + ['currency' => 'Moeda do projecto', 'expected_start_date' => 'Data prevista de início', 'entity_stage' => 'Situação da entidade'];
$missingLabels = array_map(fn ($key) => $labels[$key] ?? $key, array_values(array_unique($missing)));
return ['version' => self::VERSION, 'enabled' => $enabled, 'period_covered' => $period, 'scope_covered' => $covered, 'mode' => $mode, 'reason' => $reasons, 'missing_fields' => array_values(array_unique($missing)), 'missing_labels' => $missingLabels, 'questions' => $this->questions(), 'requirements' => $requirements, 'authority' => $authority, 'authority_source' => $authoritySource, 'next' => $base === false ? 'Rever o âmbito/período; este lote não cobre o pedido' : ($mode === 'indeterminate' ? 'Completar condições do enquadramento' : ($mode === 'authorization' ? 'Preparar estudo e documentos do pedido de autorização' : 'Preparar documentos do pedido de mero registo')), 'calculation_notice' => 'O regime procedimental não altera a taxa de IVA. Reutilize a simulação geral apenas no seu âmbito e período; benefícios, apuramento final e conversão cambial não são presumidos.', 'source' => ['url' => self::SOURCE, 'sha256' => self::HASH], 'notice' => 'Preparação informativa baseada em declarações; documentos associados não constituem validação jurídica, certificado ou submissão à Administração.'];
}
}