Compare commits

...
Author SHA1 Message Date
nala f276781876 Keep sector reference byte verification identical across Windows and Git
tests / ci (push) Canceled after 0s
2026-11-11 14:13:51 +02:00
nala 542ac2ff2c Integrate mining and energy references into sector and dossier journeys 2026-11-11 14:10:15 +02:00
nala 4971f8935b Record reconciled profile and six-menu delivery installed in 6d3bf293 2026-11-11 12:39:23 +02:00
nala 6d3bf2937e Merge remote-tracking branch 'origin/main' into entrega/directiva-20261110
tests / ci (push) Canceled after 0s
2026-11-11 12:31:10 +02:00
nala eca6719135 Unify institution profiles and reduce primary menu to six groups 2026-11-11 12:11:52 +02:00
paulomassigue a400802b63 Merge branch 'front' of https://git.tempo.co.mz/nala/Investidor-V2
tests / ci (push) Canceled after 0s
2026-11-11 11:53:57 +02:00
paulomassigue 1b8b9f414a . 2026-11-11 11:53:41 +02:00
paulomassigue 70423474d2 navbar fixa durante a rolagem 2026-11-11 11:46:12 +02:00
nala ee23a2cc2c Record financial editorial a59142ca installation and preservation 2026-11-11 11:39:22 +02:00
nala a59142caa2 Present financial institutions with professional investor-facing descriptions and products
tests / ci (push) Canceled after 0s
2026-11-11 11:19:27 +02:00
nala 8ebe734d0d Record PEC correction 8f295f05 installation and data preservation 2026-11-11 11:03:50 +02:00
nala 8f295f0551 Preserve documented turnover corrections in PEC viability estimates
tests / ci (push) Canceled after 0s
2026-11-11 10:55:53 +02:00
nala a2033dd4a0 Record viability simulation a708f079 installation and data preservation 2026-11-11 10:46:21 +02:00
nala a708f079b3 Clarify viability simulation purpose and preserve PEC cash-flow hypotheses
tests / ci (push) Canceled after 0s
2026-11-11 10:37:33 +02:00
nala aae79c712d Record PEC bcbbe51c installation and verified preservation 2026-11-11 10:28:46 +02:00
nala bcbbe51c2f Prepare PEC obligation total and calendar with private recovery and reports
tests / ci (push) Canceled after 0s
2026-11-11 09:59:54 +02:00
nala e549003e5d Record PPC fbff2660 installation and verified preservation 2026-11-11 09:05:24 +02:00
nala fbff2660f2 Preparar obriga??o PPC ordin?ria com recupera??o e relat?rio no dossier
tests / ci (push) Canceled after 0s
2026-11-11 08:55:14 +02:00
nala 192a7e35e5 Registar retoma educativa instalada em 6805b606 e preservação conferida 2026-11-11 08:46:32 +02:00
nala 6805b606a1 Complete durable private Education review preparation and recovery
tests / ci (push) Canceled after 0s
2026-11-11 08:21:56 +02:00
nala bbc3c1c9bc Registar combinação CBF15 instalada em 9c067d6a e dados preservados 2026-11-11 07:45:02 +02:00
nala 9c067d6ae8 Completar combinação CBF15 com saldos anteriores e conferir PEC residual
tests / ci (push) Canceled after 0s
2026-11-11 07:35:04 +02:00
nala 67cd583b94 Confirmar 270 instituições publicadas e consulta financeira por localização 2026-11-10 22:55:05 +02:00
nala 0f280d2d6d Confirmar acesso ao editor no Edge com a sessão autenticada correcta 2026-11-10 22:12:24 +02:00
nala 0e8da995e0 Diagnosticar recusa do editor em sessão Safari distinta do login Edge 2026-11-10 22:11:07 +02:00
nala ff293940a6 Registar retoma editorial instalada em 4f3dbcf8 e conteúdo ainda por publicar 2026-11-10 22:02:56 +02:00
nala 4f3dbcf864 Conservar progresso editorial entre renovações MFA e tornar a retoma visível
tests / ci (push) Canceled after 0s
2026-11-10 21:55:42 +02:00
nala d830fbca50 Registar instalação ef24cbdc e disponibilização editorial ainda por confirmar 2026-11-10 21:19:01 +02:00
nala ef24cbdc92 Conferir e publicar instituições pelo editor assistido com MFA nativo
tests / ci (push) Canceled after 0s
2026-11-10 21:09:18 +02:00
nala 5a5cc9e815 Integrate supplied institutions by location category and type through investor dossiers 2026-11-10 20:59:24 +02:00
nala 5ff9402fd0 Prepare supplied financial directory with product coverage and native dossier continuity 2026-11-10 18:51:23 +02:00
nala c8e0a7ee39 Record installed APIEX SWOT and prominent Concursos delivery receipt 2026-11-10 18:23:31 +02:00
nala 1c75a30a60 Preserve S08 hash lifecycle while revealing linked SWOT tasks
tests / ci (push) Canceled after 0s
2026-11-10 18:17:13 +02:00
nala c790159250 Restore prominent Concursos dropdown before authorized APIEX and SWOT delivery
tests / ci (push) Canceled after 0s
2026-11-10 18:07:50 +02:00
nala 37854d0231 Complete APIEX documentary preparation and private contextual SWOT in existing dossiers 2026-11-10 18:00:06 +02:00
nala 7476dc3430 feat: carry documented investment credits within original project window
tests / ci (push) Canceled after 0s
2026-11-10 16:17:48 +02:00
nala d216830b06 fix: unify horizontal portal menus and remove discovery duplicates
tests / ci (push) Canceled after 0s
2026-11-10 15:48:23 +02:00
nala d5cb41b111 feat: apply investment credit five-exercise window
tests / ci (push) Canceled after 0s
2026-11-10 15:35:24 +02:00
nala 89e6d72c1e feat: calculate qualified investment credits in annual settlement
tests / ci (push) Canceled after 0s
2026-11-10 15:09:31 +02:00
nala 7c8d4726e9 feat: qualify treaty interest under governmental financing agreements
tests / ci (push) Canceled after 0s
2026-11-10 14:18:47 +02:00
nala b10b35f308 feat: support public-payer treaty exemption and exact economic adjustments
tests / ci (push) Canceled after 0s
2026-11-10 13:54:06 +02:00
nala 60b52fc8c7 feat: derive qualified Portugal treaty credit limits
tests / ci (push) Canceled after 0s
2026-11-10 13:38:56 +02:00
nala ac67f9c13a feat: reconcile L6 economic income and extend L8 credits
tests / ci (push) Canceled after 0s
2026-11-10 13:23:11 +02:00
nala c359364de3 feat(portal): deliver ordinary national credit fraction and education continuity
tests / ci (push) Canceled after 0s
2026-11-10 12:47:57 +02:00
nala ff726e577f docs(portal): diagnose browser bridge and record scoped review
tests / ci (push) Canceled after 0s
2026-11-10 11:11:40 +02:00
nala 03bcaf38cd fix(licensing): keep hash target revealed after next-stage updates 2026-11-10 11:10:53 +02:00
nala 659c039b27 docs(licensing): record S08 coverage and keep visual review and installation separate 2026-11-10 11:01:29 +02:00
nala 6734441855 fix(licensing): simplify S08 and reveal linked stage without losing context 2026-11-10 11:00:56 +02:00
nala 95def7f75e docs(portal): preserve S08 pending scope after owner clarification 2026-11-10 10:52:49 +02:00
nala 786d5f78d1 docs(portal): record Portal V4 source recovery for S08 2026-11-10 10:49:00 +02:00
nala 5e00f1ee9e docs(portal): map scoped commits and preserve S08 and visual pending work 2026-11-10 10:42:40 +02:00
nala 252f38bb0a feat(portal): expose bounded public sitemap 2026-11-10 10:41:50 +02:00
nala 7f13149723 feat(opportunities): curate highlights with native authority and audited confirmation 2026-11-10 10:41:50 +02:00
nala dcecd25102 feat(desk): print authorized saved pre-dossier 2026-11-10 10:41:49 +02:00
nala 4e07557cb1 fix(opportunities): distinguish missing verification and improve metadata 2026-11-10 10:41:48 +02:00
nala 4e61480272 fix(portal): identify operator and institutional support contact 2026-11-10 10:41:47 +02:00
nala 377c40a21a fix(portal): preserve territorial totals and legacy redirect context 2026-11-10 10:41:46 +02:00
paulomassigue 2209b83820 login de cadastro padronizado
tests / ci (push) Canceled after 0s
2026-11-09 15:05:20 +02:00
paulomassigue 67aa015b4c Merge branch 'front' of https://git.tempo.co.mz/nala/Investidor-V2
tests / ci (push) Waiting to run
2026-11-05 14:59:29 +02:00
paulomassigue 4f630b9f2a pesquisa avancada 2026-11-05 14:54:39 +02:00
paulomassigue 5859a9810e Merge branch 'main' of https://git.tempo.co.mz/nala/Investidor-V2
tests / ci (push) Canceled after 0s
2026-11-05 12:21:50 +02:00
paulomassigue dab85aee96 correcao de conflitos 2026-11-05 12:21:25 +02:00
nala 12cecd061b commit de menus
tests / ci (push) Canceled after 0s
2026-11-05 08:36:28 +02:00
paulomassigue 705dc69d68 pesquisa intelignte
tests / ci (push) Canceled after 0s
2026-11-04 15:52:55 +02:00
paulomassigue 6da444ae2e .
tests / ci (push) Canceled after 0s
2026-11-04 15:10:29 +02:00
nala 7e351348af feat: filter public map indicators by indicator and reference period 2026-11-04 12:55:03 +02:00
paulomassigue c5756c0282 pesquisa avancada no portal 2026-11-04 12:38:21 +02:00
nala 3f50f66633 feat: display precise public labour and economic observations in map context
tests / ci (push) Canceled after 0s
2026-11-04 11:45:42 +02:00
nala a9241ff26b feat: add workforce evidence and sourced economic context map layers 2026-11-04 11:27:37 +02:00
nala 21ebad73e7 feat: add public resource and infrastructure evidence layers to catalogue map 2026-11-04 11:16:01 +02:00
nala bbc05f1df6 feat: show immediate administrative subdivisions and map coverage gaps 2026-11-04 10:58:28 +02:00
nala 6dde36b5ca fix: preserve territorial scope in catalogue CSV and record local visual QA 2026-11-04 10:49:22 +02:00
nala dc0bbd74d5 test: verify local main against isolated PostGIS QA 2026-11-04 10:16:58 +02:00
nala 66a5dbf311 docs: record verified local reconciliation integration without push 2026-11-04 09:18:28 +02:00
paulomassigue bf2c054943 Merge branch 'main' of https://git.tempo.co.mz/nala/Investidor-V2 into front 2026-11-03 15:31:43 +02:00
paulomassigue f3455432cf . 2026-11-03 15:31:25 +02:00
286 changed files with 63989 additions and 925 deletions
+5
View File
@@ -119,3 +119,8 @@ INVESTMOZ_TAX_RULE_APPROVERS=
INVESTMOZ_TAX_RULE_PUBLISHERS=
INVESTMOZ_ZONE_TERRITORY_REVIEWERS=
# Local LibreTranslate service and installed language models.
LIBRETRANSLATE_URL=http://127.0.0.1:5000
LIBRETRANSLATE_TIMEOUT=60
LIBRETRANSLATE_API_KEY=
+13
View File
@@ -1,3 +1,16 @@
## Uniformização pública e menu — decisão expressa do proprietário,11/11/2026
Lista e detalhe das instituições financeiras devem usar a mesma apresentação:descrição única,categoria/tipo sem repetição,produtos/serviços,contactos e moradas úteis apenas quando disponíveis. Não apresentar secções vazias,avisos genéricos repetidos,notas de publicação/importação,preços antigos por reconfirmar ou a mesma informação várias vezes. Deduplicar apenas a apresentação,conservando dados e fontes no editor. Usar Quorus e Moza Banco como exemplos de conferência.
Menu principal:Início,Descoberta,Realização,Suporte,Sobre nós,Mais. Descoberta:Concursos e anúncios,Localizações,Instituições financeiras,Catálogos relacionados. Realização:Iniciar investimento,Licenciamento,Acesso à terra. Suporte:Biblioteca,Legislação,Serviços ao investidor,Acompanhamento do projecto,Apoio ao investidor. Preservar conteúdo deSobre nós/Mais,contexto dos dossiers e permissões. Escolha de apresentação:acessos de trabalho antes no nível principal passam paraMais,sem novas concessões.
## Comunicação pública — orientação expressa do proprietário,11/11/2026
Apresentar conteúdos públicos com linguagem profissional,clara e apelativa,centrada na entidade,oportunidade,produtos e serviços úteis ao investidor. Não usar descrições de importação,cadastro,fichas,ficheiros ou documentos geradores como apresentação comercial. Preservar as fontes,versões e rastreabilidade no cadastro/editor;ligações institucionais e datas úteis podem ser consultadas separadamente. Não inventar ofertas,condições,disponibilidade,cobertura ou garantias para preencher lacunas. Esta orientação aplica-se transversalmente aos conteúdos do portal.
## Simulação de viabilidade — clarificação expressa do proprietário,11/11/2026
O InvesteMoz simula impostos para avaliar a viabilidade do negócio; não actua como entidade que executa pagamentos. Apresentar PPC/PEC e distribuição por meses como estimativas fiscais e projecções de tesouraria,com fontes e pressupostos. Montantes já pagos,quando fornecidos,são dados documentais para o cálculo. Não confundir uma hipótese de caixa com pagamento efectuado,declaração submetida ou cumprimento fiscal certificado. Preservar os cálculos,históricos e dados aceites; não criar serviços de cobrança/execução de pagamentos como requisito de conclusão deste simulador.
# Instruções permanentes do projeto InvesteMoz
## Encerramento e reabertura do dossier — decisão do proprietário, 03/11/2026
@@ -0,0 +1,83 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Throwable;
class TranslationApiController extends Controller
{
public function languages(): JsonResponse
{
try {
$response = Http::connectTimeout(2)->timeout(config('services.translation.timeout'))->get($this->url().'/languages');
if ($response->successful() && is_array($response->json())) {
return response()->json($response->json())->header('Cache-Control', 'no-store');
}
} catch (Throwable) {
}
return response()->json(['message' => 'Serviço de tradução temporariamente indisponível.'], 503);
}
public function translate(Request $request): JsonResponse
{
$data = $request->validate([
'text' => is_array($request->input('text'))
? 'required|array|min:1|max:8'
: 'required|string|max:10000',
'text.*' => 'required|string|max:10000',
'source_lang' => 'required|string|max:20',
'target_lang' => 'required|string|max:20',
'is_html' => 'boolean',
]);
$isBatch = is_array($data['text']);
if ($isBatch && mb_strlen(implode('', $data['text'])) > 10000) {
return response()->json(['message' => 'O lote de tradução excede o limite permitido.'], 422);
}
if ($data['source_lang'] === $data['target_lang']) {
return response()->json([
'original_text' => $data['text'],
'translated_text' => $data['text'],
'source_lang' => $data['source_lang'],
'target_lang' => $data['target_lang'],
])->header('Cache-Control', 'no-store');
}
$payload = ['q' => $data['text'], 'source' => $data['source_lang'], 'target' => $data['target_lang'], 'format' => ($data['is_html'] ?? false) ? 'html' : 'text'];
if (config('services.translation.api_key')) {
$payload['api_key'] = config('services.translation.api_key');
}
try {
$response = Http::connectTimeout(2)->timeout(config('services.translation.timeout'))->post($this->url().'/translate', $payload);
$translated = $response->json('translatedText');
$validTranslation = $isBatch
? is_array($translated)
&& count($translated) === count($data['text'])
&& ! array_filter($translated, fn ($text) => ! is_string($text) || $text === '')
: is_string($translated) && $translated !== '';
if ($response->successful() && $validTranslation) {
return response()->json([
'original_text' => $data['text'],
'translated_text' => $translated,
'source_lang' => $data['source_lang'],
'target_lang' => $data['target_lang'],
])->header('Cache-Control', 'no-store');
}
} catch (Throwable) {
}
return response()->json(['message' => 'Serviço de tradução temporariamente indisponível.'], 503)->header('Cache-Control', 'no-store');
}
private function url(): string
{
return rtrim((string) config('services.translation.url'), '/');
}
}
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Models\Opportunity;
use App\Services\InvestmentGeography;
use App\Services\SectorOrganization;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
@@ -20,8 +21,9 @@ class HomeController extends Controller
$locale = $validated['locale'] ?? 'pt';
$data = Cache::remember("portal_home_sector_organization_v1_{$locale}", now()->addMinutes(10), function () {
$data = Cache::remember("portal_home_territorial_v2_{$locale}", now()->addMinutes(10), function () {
$sectors = SectorOrganization::catalog();
$statistics = app(InvestmentGeography::class)->opportunityStatistics();
$featured = Opportunity::with('sector:id,name,slug')
->published()->where('is_featured', true)
@@ -48,9 +50,9 @@ class HomeController extends Controller
'url' => "/oportunidades/{$o->slug}",
])->values()->all(),
'stats' => [
'opportunities' => Opportunity::published()->count(),
'opportunities' => $statistics['opportunities'],
'sectors' => $sectors->count(),
'provinces' => Opportunity::published()->distinct('province')->count('province'),
'provinces' => $statistics['provinces'],
],
];
});
@@ -21,11 +21,13 @@ use App\Services\EducationWorkQueue;
use App\Services\InvestmentGeography;
use App\Services\PortalAudit;
use Carbon\CarbonImmutable;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
use Inertia\Inertia;
use Inertia\Response;
@@ -166,6 +168,7 @@ class EducationController extends Controller
Inertia::encryptHistory();
return tap(Inertia::render('education/declaration', ['declaration' => $row, 'kind' => $kind, 'actorId' => $request->user()->id, 'today' => now()->toDateString(),
'preparation' => app(EducationReviews::class)->preparation($request->user(), $record),
'history' => app(EducationReviews::class)->history($request->user(), $record),
'backUrl' => route('management.education.index', $filters, false), 'references' => $references])->toResponse($request), static fn (\Symfony\Component\HttpFoundation\Response $response) => $response->headers->set('Cache-Control', 'private, no-store'));
}
@@ -203,6 +206,29 @@ class EducationController extends Controller
return back()->with('success', 'Declaração registada para revisão independente.');
}
public function reviewPreparation(Request $request, string $kind, string $id): JsonResponse
{
$service = app(EducationReviews::class);
app(EducationAccess::class)->authorize($request->user());
if ($request->isMethod('POST')) {
// Like CRM preparations, retain partial text before business validation.
$input = json_decode($request->getContent(), true);
if (! is_array($input)) {
throw ValidationException::withMessages(['snapshot' => 'A preparação deve ser enviada em JSON.']);
}
$view = $service->savePreparation($request->user(), $kind, $id, $input);
} else {
$expected = $request->validate(['actor_id' => 'sometimes|required|integer|min:1']);
abort_if(isset($expected['actor_id']) && (int) $expected['actor_id'] !== $request->user()->id, 409, 'A conta mudou. Actualize a entrada antes de recuperar.');
$record = match ($kind) {
'demands' => LabourDemand::findOrFail($id), 'capacities' => TrainingCapacity::findOrFail($id), default => abort(404)
};
$view = $service->preparation($request->user(), $record);
}
return response()->json($view)->header('Cache-Control', 'private, no-store');
}
public function review(Request $request, string $kind, string $id): RedirectResponse
{
$result = app(EducationReviews::class)->decide($request->user(), $kind, $id, $request->all());
@@ -4,10 +4,16 @@ namespace App\Http\Controllers;
use App\Models\Sector;
use App\Services\DocumentManagementAccess;
use App\Services\FundingDossierPreparation;
use App\Services\FundingInstitutionClassification;
use App\Services\FundingInstitutionDetails;
use App\Services\FundingProgrammeReferences;
use App\Services\FundingProgrammes;
use App\Services\InvestmentGeography;
use App\Services\InvestorContext;
use App\Services\InvestorPath;
use App\Services\Procurement;
use App\Services\VirtualDesk;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
@@ -22,20 +28,38 @@ final class FundingProgrammeController extends Controller
{
public function reference(Request $request, string $code, FundingProgrammeReferences $references): \Symfony\Component\HttpFoundation\Response
{
return tap(Inertia::render('funding/reference', ['reference' => $references->preview($request->user(), $code)])->toResponse($request),
$plan = $references->preview($request->user(), $code);
if ($request->expectsJson()) {
return response()->json($plan)->header('Cache-Control', 'private, no-store');
}
return tap(Inertia::render('funding/reference', ['reference' => $plan])->toResponse($request),
static fn (\Symfony\Component\HttpFoundation\Response $response) => $response->headers->set('Cache-Control', 'private, no-store'));
}
public function prepareReference(Request $request, string $code, FundingProgrammeReferences $references): RedirectResponse
public function prepareReference(Request $request, string $code, FundingProgrammeReferences $references): RedirectResponse|JsonResponse
{
$data = $request->validate(['hash' => 'required|string|size:64']);
$result = $references->prepare($request->user(), $code, $data['hash']);
if ($request->expectsJson()) {
$reference = $references->preview($request->user(), $code);
$service = app(FundingProgrammes::class);
$matches = $service->matchesReference($request->user(), $result['id'], $reference['draft']);
$publication = $matches ? $service->preview($request->user(), $result['id']) : null;
return response()->json([...$result, 'matches_reference' => $matches, 'publication' => $publication ? ['version' => $publication['version'], 'hash' => $publication['hash']] : null])->header('Cache-Control', 'private, no-store');
}
return redirect('/management/fundos/'.$result['id'])->with('success', $result['created']
? 'Rascunho preparado com as fontes indicadas. Ainda não está publicado.'
: 'Esta referência já tem ficha. Aberta a versão existente, sem substituir alterações ou acrescentar chamadas.');
}
public function assisted(Request $request, FundingProgrammeReferences $references): \Symfony\Component\HttpFoundation\Response
{
return tap(Inertia::render('funding/assisted', ['batch' => $references->assisted($request->user())])->toResponse($request), static fn ($response) => $response->headers->set('Cache-Control', 'private, no-store'));
}
public function manage(Request $request, FundingProgrammes $service): \Symfony\Component\HttpFoundation\Response
{
$service->guard($request->user());
@@ -43,7 +67,7 @@ final class FundingProgrammeController extends Controller
$items->through(fn ($row) => ['id' => $row->id, 'title' => json_decode($row->draft, true)['title'] ?? 'Rascunho sem título',
'version' => (int) $row->version, 'published' => $row->published_version_id !== null]);
return tap(Inertia::render('funding/manage', ['items' => $items])->toResponse($request),
return tap(Inertia::render('funding/manage', ['items' => $items, 'references' => app(FundingProgrammeReferences::class)->choices($request->user())])->toResponse($request),
static fn (\Symfony\Component\HttpFoundation\Response $response) => $response->headers->set('Cache-Control', 'private, no-store'));
}
@@ -63,6 +87,7 @@ final class FundingProgrammeController extends Controller
return tap(Inertia::render('funding/edit', ['record' => $row ? ['id' => $row->id, 'code' => $row->code, 'version' => (int) $row->version,
'draft' => json_decode($row->draft, true), 'published' => $row->published_version_id !== null] : null,
'references' => Arr::only($references, ['organizations', 'sectors', 'territories', 'documents']),
'categories' => FundingInstitutionClassification::CATEGORIES, 'institutionTypes' => FundingInstitutionClassification::TYPES, 'institutionNatures' => FundingInstitutionDetails::NATURES, 'needs' => FundingInstitutionDetails::NEEDS,
'kinds' => FundingProgrammes::KINDS, 'windowModes' => FundingProgrammes::WINDOWS,
'history' => $id ? DB::table('funding_programme_versions')->where('programme_id', $id)->orderByDesc('version')->get(['version', 'reason', 'published_at']) : []])->toResponse($request),
static fn (\Symfony\Component\HttpFoundation\Response $response) => $response->headers->set('Cache-Control', 'private, no-store'));
@@ -80,9 +105,12 @@ final class FundingProgrammeController extends Controller
return response()->json($service->preview($request->user(), $id))->header('Cache-Control', 'private, no-store');
}
public function publish(Request $request, string $id, FundingProgrammes $service): RedirectResponse
public function publish(Request $request, string $id, FundingProgrammes $service): RedirectResponse|JsonResponse
{
$service->publish($request->user(), $id, $request->all());
$versionId = $service->publish($request->user(), $id, $request->all());
if ($request->expectsJson()) {
return response()->json(['id' => $id, 'version_id' => $versionId, 'published' => true])->header('Cache-Control', 'private, no-store');
}
return back()->with('success', 'Nova versão publicada. Os interesses anteriores conservam a versão guardada.');
}
@@ -118,15 +146,22 @@ final class FundingProgrammeController extends Controller
public function index(Request $request, FundingProgrammes $service): \Symfony\Component\HttpFoundation\Response
{
$filters = $request->validate(['q' => 'nullable|string|max:150', 'kind' => 'nullable|in:fund,programme,incentive',
$filters = $request->validate(['q' => 'nullable|string|max:150', 'kind' => 'nullable|in:institution,fund,programme,incentive',
'category' => ['nullable', Rule::in(array_keys(FundingInstitutionClassification::CATEGORIES))], 'institution_type' => ['nullable', Rule::in(array_keys(FundingInstitutionClassification::TYPES))], 'location_mode' => 'nullable|in:physical,coverage', 'include_historical' => 'nullable|boolean',
'need' => ['nullable', Rule::in(array_keys(FundingInstitutionDetails::NEEDS))],
'context' => 'nullable|uuid', 'opportunity' => 'nullable|string|max:150',
'territory' => ['nullable', 'uuid', Rule::in(app(InvestmentGeography::class)->publicReferences()->pluck('id')->all())],
'sector' => ['nullable', 'integer', Rule::in(Sector::choices()->whereNotNull('published_at')->pluck('id')->all())],
'certainty' => 'nullable|in:include_unknown,documented', 'state' => 'nullable|in:open,closed,upcoming,unknown']);
$filters = array_filter($filters, fn ($value) => $value !== null && $value !== '');
$dossier = $request->user() && ! empty($filters['context']) ? app(FundingDossierPreparation::class)->context($request->user(), $filters['context'], $filters['opportunity'] ?? null) : null;
if ($dossier && empty($filters['territory']) && $dossier['territory']) {
$filters['territory'] = $dossier['territory'];
}
$query = $service->catalogueQuery($filters);
if (! empty($filters['q'])) {
$term = '%'.str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], $filters['q']).'%';
$query->where(fn ($q) => $q->whereRaw("v.payload->>'title' ilike ?", [$term])->orWhereRaw("v.payload->>'summary' ilike ?", [$term]));
$query->where(fn ($q) => $q->whereRaw("v.payload->>'title' ilike ?", [$term])->orWhereRaw("v.payload->>'summary' ilike ?", [$term])->orWhereRaw("(v.payload->'aliases')::text ilike ?", [$term])->orWhereRaw("EXISTS (SELECT 1 FROM jsonb_array_elements(COALESCE(v.payload->'locations','[]'::jsonb)) l WHERE l->>'label' ilike ? OR l->>'address' ilike ? OR l->>'location_text' ilike ?)", [$term, $term, $term]));
}
if (! empty($filters['kind'])) {
$query->whereRaw("v.payload->>'kind' = ?", [$filters['kind']]);
@@ -138,7 +173,7 @@ final class FundingProgrammeController extends Controller
return [...$record, 'filter_explanation' => $service->filterExplanation($record, $filters)];
});
return tap(Inertia::render('funding/index', ['items' => $items, 'filters' => $filters, 'kinds' => FundingProgrammes::KINDS,
return tap(Inertia::render('funding/index', ['items' => $items, 'filters' => $filters, 'categories' => FundingInstitutionClassification::CATEGORIES, 'institutionTypes' => FundingInstitutionClassification::TYPES, 'kinds' => FundingProgrammes::KINDS, 'needs' => FundingInstitutionDetails::NEEDS, 'dossier' => $dossier,
'options' => ['territories' => app(InvestmentGeography::class)->publicReferences()->orderBy('name')->get(['id', 'name']),
'sectors' => Sector::choices()->whereNotNull('published_at')->orderBy('name')->get(['id', 'name'])], 'management' => $service->access($request->user())])
->toResponse($request), static fn (\Symfony\Component\HttpFoundation\Response $response) => $response->headers->set('Cache-Control', 'private, no-store'));
@@ -146,14 +181,38 @@ final class FundingProgrammeController extends Controller
public function show(Request $request, string $id, FundingProgrammes $service): \Symfony\Component\HttpFoundation\Response
{
$request->validate(['context' => 'nullable|uuid', 'opportunity' => 'nullable|string|max:150', 'need' => ['nullable', Rule::in(array_keys(FundingInstitutionDetails::NEEDS))], 'territory' => 'nullable|uuid', 'category' => 'nullable|string|max:80', 'institution_type' => 'nullable|string|max:80', 'location_mode' => 'nullable|in:physical,coverage']);
$dossier = $request->user() && $request->filled('context') ? app(FundingDossierPreparation::class)->context($request->user(), $request->string('context')->toString(), $request->query('opportunity')) : null;
if ($dossier && ! $request->filled('territory') && $dossier['territory']) {
$request->merge(['territory' => $dossier['territory']]);
}
$record = $service->published($id);
$destinations = [];
if ($request->user()) {
foreach (app(InvestorPath::class)->contexts($request->user()) as $item) {
$case = app(VirtualDesk::class)->case($request->user(), $item['id']);
$state = app(InvestorContext::class)->state($case);
$choices = $state['opportunity_choices'] ?? [];
if ($choices === []) {
$destinations[] = ['id' => $item['id'], 'opportunity' => null, 'title' => $item['title']];
}
foreach ($choices as $choice) {
$destinations[] = ['id' => $item['id'], 'opportunity' => $choice['opportunity']['key'], 'title' => $item['title'].' — '.$choice['opportunity']['title']];
}
}
}
$saved = $request->user() ? DB::table('funding_programme_interests as i')->join('funding_programme_versions as v', 'v.id', '=', 'i.version_id')
->where('i.programme_id', $id)->where('i.user_id', $request->user()->id)->whereNull('i.removed_at')->first(['v.version', 'i.saved_at']) : null;
return tap(Inertia::render('funding/show', ['record' => $record, 'saved' => $saved, 'authenticated' => $request->user() !== null])
return tap(Inertia::render('funding/show', ['record' => $record, 'saved' => $saved, 'authenticated' => $request->user() !== null, 'dossier' => $dossier, 'selection' => [...$request->only(['need', 'territory', 'context', 'opportunity', 'category', 'institution_type', 'location_mode']), 'territory_label' => app(InvestmentGeography::class)->publicReferences()->where('id', $request->query('territory'))->value('name') ?? 'Local por seleccionar'], 'needs' => FundingInstitutionDetails::NEEDS, 'destinations' => $destinations])
->toResponse($request), static fn (\Symfony\Component\HttpFoundation\Response $response) => $response->headers->set('Cache-Control', 'private, no-store'));
}
public function dossier(Request $request, string $id, FundingDossierPreparation $service): JsonResponse
{
return response()->json($service->save($request->user(), $id, $request->all()))->header('Cache-Control', 'private, no-store');
}
public function assess(Request $request, string $id, FundingProgrammes $service): JsonResponse
{
return response()->json($service->assess($id, $request->all()))->header('Cache-Control', 'private, no-store');
@@ -0,0 +1,174 @@
<?php
namespace App\Http\Controllers;
use App\Models\ExistingBusiness;
use App\Models\Sector;
use App\Services\EditorialReading;
use App\Services\FederatedSearch;
use App\Services\InvestmentCatalogue;
use App\Services\InvestmentGeography;
use App\Services\InvestmentZones;
use App\Services\SearchTerms;
use App\Services\SectorOrganization;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
use Inertia\Inertia;
use Inertia\Response;
class GlobalSearchController extends Controller
{
public function __invoke(Request $request, InvestmentCatalogue $catalogue, SearchTerms $search, FederatedSearch $federated): Response
{
$input = $request->validate(['q' => ['nullable', 'string', 'max:120'], 'page' => ['nullable', 'integer', 'min:1']]);
$term = trim($input['q'] ?? '');
$tokens = $search->tokens($term);
$results = ['data' => [], 'total' => 0, 'current_page' => 1, 'last_page' => 1, 'per_page' => 5, 'unavailable' => []];
if ($tokens !== []) {
$factories = [
'Páginas e serviços' => function () use ($search, $tokens, $term) {
$pages = array_values(array_filter($this->pages(), fn ($page) => $search->matches(implode(' ', $page), $tokens)
&& $search->score($page['title'], $term, $tokens) > 0));
usort($pages, fn ($a, $b) => $search->score($b['title'], $term, $tokens) <=> $search->score($a['title'], $term, $tokens)
?: strcmp($a['title'], $b['title']) ?: strcmp($a['url'], $b['url']));
return ['count' => count($pages), 'fetch' => fn ($offset, $limit) => array_slice($pages, $offset, $limit)];
},
'Oportunidades' => function () use ($catalogue, $search, $tokens, $term) {
$query = $catalogue->query(['include_national' => true]);
foreach ($tokens as $token) {
$query->where(function ($match) use ($catalogue, $search, $token) {
$search->orFields($match, ['opportunities.title', 'opportunities.summary', 'opportunities.description', 'opportunities.reference', 'opportunities.province'], $token);
$match->orWhereHas('sector', fn ($sector) => $search->constrain($sector, ['name'], [$token]));
$match->orWhereExists(fn ($location) => $location->selectRaw('1')->from('geo_locations as search_location')
->whereIn('search_location.id', app(InvestmentGeography::class)->publicReferences()->select('id'))
->where(fn ($name) => $search->orFields($name, ['search_location.name'], $token))
->where(fn ($direct) => $direct->whereColumn('search_location.id', 'opportunities.primary_territory_id')
->orWhereExists(fn ($link) => $link->selectRaw('1')->from('opportunity_locations as search_link')
->whereColumn('search_link.opportunity_id', 'opportunities.id')->whereColumn('search_link.geo_location_id', 'search_location.id'))));
if (preg_match('/^[a-z0-9]+$/', $token)) {
$match->orWhereIn('opportunities.id', $catalogue->query(['q' => $token, 'include_national' => true])->reorder()->select('opportunities.id'));
}
});
}
return $this->source($query, $search, $term, $tokens, 'opportunities.title', 'opportunities.id',
fn ($r) => ['title' => $r->title, 'description' => $r->summary, 'url' => '/oportunidades/'.rawurlencode($r->slug)]);
},
'Negócios' => function () use ($search, $tokens, $term) {
$query = ExistingBusiness::query()->published();
$search->constrain($query, ['public_title', 'activity_code', 'objective'], $tokens);
return $this->source($query, $search, $term, $tokens, 'public_title', 'existing_businesses.id',
fn ($r) => ['title' => $r->public_title, 'description' => $r->objective, 'url' => '/negocios/'.rawurlencode($r->public_slug)]);
},
'Carteira: projectos, clusters e corredores' => function () use ($search, $tokens, $term) {
$query = InvestmentPortfolioController::visible();
$search->constrain($query, ['investment_dossiers.title', 'investment_dossiers.content'], $tokens);
return $this->source($query, $search, $term, $tokens, 'investment_dossiers.title', 'investment_dossiers.id',
fn ($r) => ['title' => $r->title, 'description' => $r->corridor_id ? 'Corredor' : ($r->cluster_id ? 'Cluster' : 'Projecto âncora'), 'url' => '/carteira/'.$r->id]);
},
'Documentos e legislação' => function () use ($search, $tokens, $term) {
$query = DB::table('portal_resources as r')->join('documents as d', 'd.id', '=', 'r.document_id')
->where('r.published', true)->where('r.rights_status', '!=', 'restricted')->where('d.rights_status', '!=', 'restricted')
->where('d.status', 'active')->where('d.data_classification', 'public')->whereNull('d.deleted_at')
->where('d.malware_scan_status', 'clean')->whereColumn('d.malware_scanned_sha256', 'd.sha256');
$search->constrain($query, ['r.title', 'r.description', 'r.document_number', 'd.issuer', 'r.source_reference', 'r.category', 'r.topic'], $tokens);
$query->select('r.id', 'r.title', 'r.description');
return $this->source($query, $search, $term, $tokens, 'r.title', 'r.id',
fn ($r) => ['title' => $r->title, 'description' => $r->description, 'url' => '/documentos/'.$r->id]);
},
'Artigos' => function () use ($search, $tokens, $term) {
if (! Schema::hasColumn('portal_resources', 'editorial_content')) {
return ['count' => 0, 'fetch' => fn ($offset, $limit) => []];
}
$query = app(EditorialReading::class)->publicResources()
->whereRaw("nullif(btrim(r.editorial_content->>'body'), '') is not null");
$search->constrain($query, ['r.title', "(r.editorial_content->>'body')"], $tokens);
$query->select('r.id', 'r.title')->selectRaw("r.editorial_content->>'body' as body");
return $this->source($query, $search, $term, $tokens, 'r.title', 'r.id',
fn ($r) => ['title' => $r->title, 'description' => Str::limit(trim(strip_tags($r->body)), 220), 'url' => '/artigos/'.$r->id]);
},
'Sectores' => function () use ($search, $tokens, $term) {
$query = Sector::query()->where('is_active', true)->whereNotNull('published_at');
$search->constrain($query, ['name', 'description', 'short_description'], $tokens);
return $this->source($query, $search, $term, $tokens, 'name', 'sectors.id',
fn ($r) => ['title' => $r->name, 'description' => $r->short_description, 'url' => SectorOrganization::href($r->slug)]);
},
'Localizações' => function () use ($search, $tokens, $term) {
$query = app(InvestmentGeography::class)->publicReferences()
->whereIn('location_type', ['province', 'district', 'admin_post', 'locality']);
$search->constrain($query, ['geo_locations.name', 'geo_locations.source_code'], $tokens);
$query->select('geo_locations.id', 'geo_locations.name', 'geo_locations.location_type');
return $this->source($query, $search, $term, $tokens, 'geo_locations.name', 'geo_locations.id',
fn ($r) => ['title' => $r->name, 'description' => ['province' => 'Província', 'district' => 'Distrito', 'admin_post' => 'Posto administrativo', 'locality' => 'Localidade'][$r->location_type],
'url' => '/oportunidades?'.http_build_query([$r->location_type.'_id' => $r->id])]);
},
'Zonas de investimento' => function () use ($search, $tokens, $term) {
$query = app(InvestmentZones::class)->visible();
$search->constrain($query, ['name', 'code'], $tokens);
return $this->source($query, $search, $term, $tokens, 'name', 'investment_zones.id',
fn ($r) => ['title' => $r->name, 'description' => $r->code, 'url' => '/zonas/'.$r->id]);
},
'Parcerias' => function () use ($search, $tokens, $term) {
$query = DB::table('portal_partnerships as p')->where('p.status', 'published');
$search->constrain($query, ['p.title', 'p.sector', 'p.province'], $tokens);
$query->select('p.id', 'p.title', 'p.sector', 'p.province');
return $this->source($query, $search, $term, $tokens, 'p.title', 'p.id',
fn ($r) => ['title' => $r->title, 'description' => implode(' · ', array_filter([$r->sector, $r->province])), 'url' => '/parcerias/'.$r->id]);
},
];
$results = $federated->page($factories, (int) ($input['page'] ?? 1), 5,
fn ($item) => $search->score($item['title'], $term, $tokens));
}
return Inertia::render('portal/search', ['query' => $term, 'results' => $results]);
}
private function source($query, SearchTerms $search, string $term, array $tokens, string $title, string $id, callable $present): array
{
$count = (clone $query)->reorder()->count();
$query->reorder();
$search->orderByRelevance($query, $title, $term, $tokens, $id);
return ['count' => $count, 'fetch' => fn ($offset, $limit) => (clone $query)->offset($offset)->limit($limit)->get()->map($present)->all()];
}
/** Public service entry points and terms used to find them. */
private function pages(): array
{
return [
['title' => 'Como investir em Moçambique', 'description' => 'Procedimentos e etapas para investir', 'url' => '/como-investir'],
['title' => 'Iniciar ou continuar investimento', 'description' => 'Preparar uma ideia e dossier de investimento', 'url' => '/investimentos/iniciar'],
['title' => 'Oportunidades de investimento', 'description' => 'Pesquisar por sector, localização e maturidade', 'url' => '/oportunidades'],
['title' => 'Carteira de investimento', 'description' => 'Projectos âncora, clusters e corredores', 'url' => '/carteira'],
['title' => 'Negócios existentes', 'description' => 'Compra, venda e participação em negócios', 'url' => '/negocios'],
['title' => 'Sectores de investimento', 'description' => 'Actividades económicas e sectores prioritários', 'url' => '/sectores'],
['title' => 'Zonas de investimento', 'description' => 'Zonas económicas especiais, ZEE, ZFI e parques industriais', 'url' => '/zonas'],
['title' => 'Contextos de investimento', 'description' => 'Formas de iniciar e preparar um investimento', 'url' => '/contextos-de-investimento'],
['title' => 'Documentos e formulários', 'description' => 'Acervo documental e formulários para investidores e empresas', 'url' => '/documentos'],
['title' => 'Formulário do investidor', 'description' => 'Dados e contacto para investir', 'url' => '/formularios/investidor'],
['title' => 'Formulário de empresa', 'description' => 'Dados e contacto para empresas', 'url' => '/formularios/empresa'],
['title' => 'Legislação e incentivos', 'description' => 'Leis, decretos, benefícios e enquadramento legal', 'url' => '/legislacao'],
['title' => 'Simulador fiscal', 'description' => 'Simular impostos e cenários fiscais de investimento', 'url' => '/simulador-fiscal'],
['title' => 'Formulários fiscais', 'description' => 'Preparação dos formulários IVA e IRPC', 'url' => '/formularios/fiscais'],
['title' => 'Terra e DUAT', 'description' => 'Apoio ao uso e aproveitamento da terra', 'url' => '/terra-duat'],
['title' => 'Bolsa de parcerias', 'description' => 'Encontrar parceiros e propostas', 'url' => '/parcerias'],
['title' => 'Apoio ao investidor', 'description' => 'Contactos e pedido de apoio', 'url' => '/apoio'],
['title' => 'Educação para investimento', 'description' => 'Conteúdos educativos e orientação', 'url' => '/educacao'],
['title' => 'Sobre o InvesteMoz', 'description' => 'Informação institucional do portal', 'url' => '/sobre'],
['title' => 'Contacto', 'description' => 'Contactar o portal InvesteMoz', 'url' => '/contacto'],
['title' => 'Política de privacidade', 'description' => 'Privacidade e tratamento de dados pessoais', 'url' => '/politica-de-privacidade'],
['title' => 'Termos de uso', 'description' => 'Condições de utilização do portal', 'url' => '/termos-de-uso'],
];
}
}
@@ -9,6 +9,8 @@ use App\Services\CorridorAgribusinessOpportunities;
use App\Services\InvestmentCatalogue;
use App\Services\InvestmentComplements;
use App\Services\InvestmentGeography;
use App\Services\InvestmentMapIndicators;
use App\Services\InvestmentZoneOperationalDetails;
use App\Services\InvestmentZones;
use App\Services\OpportunityCorridorExplorer;
use App\Services\OpportunityTextSearch;
@@ -155,6 +157,7 @@ class InvestmentDiscoveryController extends Controller
$filters = $catalogue->filters($request);
$context = $this->territorial($filters)->map(fn ($d) => ['id' => 'dossier:'.$d->id, 'title' => $d->title, 'kind' => $d->cluster_id ? 'Cluster' : ($d->corridor_id ? 'Corredor' : 'Projeto âncora'), 'url' => '/carteira/'.$d->id]);
$opportunityOnlyFilters = array_filter(Arr::only($filters, ['association_type', 'association_target', 'association_state', 'association_purpose', 'authority_id', 'chain_id', 'subsector_id', 'category', 'publication_state', 'education', 'sector', 'facets', 'project_id', 'cluster_id', 'corridor_id', 'min_amount', 'max_amount', 'currency', 'before', 'maturity_level', 'preparation']), fn ($value) => $value !== null && $value !== '' && $value !== false && $value !== []);
$quantitative = ['state' => 'not_included', 'records' => [], 'truncated' => false];
if ($opportunityOnlyFilters === [] && ! in_array($filters['dimension'] ?? null, ['cluster', 'corridor'], true)) {
$zoneFilters = $filters;
if (in_array($filters['dimension'] ?? null, ['zee', 'zfi'], true)) {
@@ -166,10 +169,12 @@ class InvestmentDiscoveryController extends Controller
$zones->where('id', $filters[$key]);
}
}
$context = $context->concat($zones->orderBy('name')->limit(30)->get(['id', 'name', 'category'])->map(fn ($z) => ['id' => 'zone:'.$z->id, 'title' => $z->name, 'kind' => ['zee' => 'ZEE', 'zfi' => 'ZFI', 'industrial_park' => 'Parque industrial'][$z->category] ?? $z->category, 'url' => '/zonas/'.$z->id]));
$zoneRows = $zones->orderBy('name')->orderBy('id')->limit(31)->get(['id', 'name', 'category', 'version_no', ...(app(InvestmentZoneOperationalDetails::class)->installed() ? ['operational_details'] : [])]);
$quantitative = app(InvestmentMapIndicators::class)->forZones($zoneRows->take(30)->values(), $zoneRows->count() > 30);
$context = $context->concat($zoneRows->take(30)->map(fn ($z) => ['id' => 'zone:'.$z->id, 'title' => $z->name, 'kind' => ['zee' => 'ZEE', 'zfi' => 'ZFI', 'industrial_park' => 'Parque industrial'][$z->category] ?? $z->category, 'url' => '/zonas/'.$z->id]));
}
return response()->json([...$catalogue->map($filters), 'territorial_context' => $context->values(), 'context_limit_per_collection' => 30]);
return response()->json([...$catalogue->map($filters), 'territorial_context' => $context->values(), 'quantitative_context' => $quantitative, 'context_limit_per_collection' => 30]);
}
public function compare(Request $request, InvestmentCatalogue $catalogue): JsonResponse
@@ -199,15 +204,16 @@ class InvestmentDiscoveryController extends Controller
{
$records = $catalogue->query($catalogue->filters($request))->limit(1000)->get();
return response()->streamDownload(function () use ($records) {
return response()->streamDownload(function () use ($records, $catalogue) {
$stream = fopen('php://output', 'w');
if ($stream === false) {
return;
}
fwrite($stream, "\xEF\xBB\xBF");
fputcsv($stream, ['Referência', 'Título', 'Sector', 'Província', 'Montante', 'Moeda', 'Maturidade', 'URL'], ';', '"', '');
fputcsv($stream, ['Referência', 'Título', 'Sector', 'Província', 'Montante', 'Moeda', 'Maturidade', 'URL', 'Localização e âmbito'], ';', '"', '');
foreach ($records as $r) {
$fields = [$r->reference, $r->title, $r->sector?->name, $r->province, $r->estimated_investment_amount, $r->estimated_investment_currency, $r->maturity_label, url('/oportunidades/'.$r->slug)];
$location = $catalogue->locationPresentation($r);
$fields = [$r->reference, $r->title, $r->sector?->name, implode(' / ', $location['provinces']), $r->estimated_investment_amount, $r->estimated_investment_currency, $r->maturity_label, url('/oportunidades/'.$r->slug), $location['summary']];
fputcsv($stream, array_map(fn ($v) => preg_match('/^[\s]*[=+@-]/u', (string) $v) ? "'".$v : $v, $fields), ';', '"', '');
}
fclose($stream);
@@ -0,0 +1,50 @@
<?php
namespace App\Http\Controllers;
use App\Services\InvestmentPreparationWorkspace;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
final class InvestmentPreparationController extends Controller
{
public function show(Request $request, string $id, InvestmentPreparationWorkspace $workspace): JsonResponse|Response
{
$request->validate(['opportunity' => 'nullable|string|max:150', 'history_scope' => 'nullable|in:dossier', 'history_before' => 'nullable|integer|min:1']);
$data = $workspace->view($request->user(), $id, $request->query('opportunity'), $request->filled('history_before') ? $request->integer('history_before') : null, $request->query('history_scope') === 'dossier');
return $request->expectsJson() ? response()->json($data) : Inertia::render('investor-path/preparation', ['workspace' => $data]);
}
public function change(Request $request, string $id, InvestmentPreparationWorkspace $workspace): JsonResponse
{
return response()->json($workspace->change($request->user(), $id, $request->all()));
}
public function preview(Request $request, string $id, InvestmentPreparationWorkspace $workspace): \Illuminate\Http\Response
{
$request->validate(['opportunity' => 'nullable|string|max:150', 'history_scope' => 'nullable|in:dossier']);
$data = $workspace->view($request->user(), $id, $request->query('opportunity'), null, $request->query('history_scope') === 'dossier');
return response()->view('investments.preparation-index', ['workspace' => $data, 'manifest' => null])->header('Content-Security-Policy', "sandbox; default-src 'none'; style-src 'unsafe-inline'")->header('X-Content-Type-Options', 'nosniff');
}
public function download(Request $request, string $id, string $exportId, InvestmentPreparationWorkspace $workspace): \Illuminate\Http\Response
{
$request->validate(['opportunity' => 'nullable|string|max:150', 'history_scope' => 'nullable|in:dossier']);
$bundle = $workspace->bundle($request->user(), $id, $request->query('opportunity'), $exportId, $request->query('history_scope') === 'dossier');
return response($bundle['bytes'], 200, ['Content-Type' => 'application/zip', 'Content-Disposition' => 'attachment; filename="preparacao-apiex-'.$id.'.zip"', 'X-Export-Id' => $exportId, 'X-Content-Type-Options' => 'nosniff']);
}
public function export(Request $request, string $id, InvestmentPreparationWorkspace $workspace): \Illuminate\Http\Response
{
$body = [...$request->all(), 'action' => 'apiex_export'];
$state = $workspace->change($request->user(), $id, $body);
$bundle = $workspace->bundle($request->user(), $id, $state['opportunity'], $body['operation_key']);
return response($bundle['bytes'], 200, ['Content-Type' => 'application/zip', 'Content-Disposition' => 'attachment; filename="preparacao-apiex-'.$id.'.zip"', 'X-Export-Id' => $bundle['export_id'], 'X-Content-Type-Options' => 'nosniff']);
}
}
@@ -16,7 +16,7 @@ class InvestmentTerritoryController extends Controller
{
public function geometries(Request $request): JsonResponse
{
$data = $request->validate(['territory_id' => ['required', 'uuid']]);
$data = $request->validate(['territory_id' => ['required', 'uuid'], 'include_children' => ['sometimes', 'boolean']]);
$profile = $request->is('minhas-oportunidades/*');
$management = $request->is('management/*') || $profile;
$actor = $request->user();
@@ -31,7 +31,7 @@ class InvestmentTerritoryController extends Controller
}
return response()->json([
...app(TerritoryGeometry::class)->context($data['territory_id'], $internal),
...app(TerritoryGeometry::class)->context($data['territory_id'], $internal, $request->boolean('include_children')),
'geometry_scope' => $internal ? 'authorized_internal' : 'public',
])->header('Cache-Control', 'private, no-store');
}
@@ -8,7 +8,9 @@ use App\Services\InvestmentGuides;
use App\Services\InvestmentZonePathContext;
use App\Services\InvestorPath;
use App\Services\LegacyContent;
use App\Services\SectorResourceReferences;
use App\Services\SectorInvestment;
use App\Services\VirtualDesk;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
@@ -32,6 +34,9 @@ class InvestorPathController extends Controller
{
abort_unless($path->available(), 404);
$data = ['contexts' => $path->contexts($request->user()), 'sectorSource' => app(SectorInvestment::class)->context($sectorSlug)];
if (isset(SectorResourceReferences::SOURCES[$sectorSlug]) && $request->filled('resource_area')) {
$data['resourceSource'] = app(SectorResourceReferences::class)->selection($sectorSlug, ['area_id' => $request->query('resource_area'), 'location_label' => $request->query('resource_location'), 'group_id' => $request->query('resource_group'), 'source_sha256' => $request->query('resource_source')]);
}
return $request->expectsJson() ? response()->json($data) : Inertia::render('investor-path/index', $data);
}
@@ -73,14 +78,28 @@ class InvestorPathController extends Controller
if ($sector !== null) {
app(SectorInvestment::class)->context($sector);
}
$reference = $request->input('sector_reference');
if ($reference !== null) {
abort_unless(isset(SectorResourceReferences::SOURCES[$sector]) && is_array($reference), 422);
app(SectorResourceReferences::class)->selection($sector, $reference);
}
$source = $request->input('content_resource_id');
if ($source !== null) {
$resourceId = filter_var($source, FILTER_VALIDATE_INT);
abort_unless(is_int($resourceId) && $resourceId !== 0, 422);
app(LegacyContent::class)->articleForResource($resourceId);
}
$id = DB::transaction(function () use ($path, $request, $source, $sector, $zone): string {
$id = DB::transaction(function () use ($path, $request, $source, $sector, $zone, $reference): string {
if ($reference !== null) {
DB::table('users')->where('id', $request->user()->id)->lockForUpdate()->first();
}
$existingReferenceCase = $reference !== null ? DB::table('desk_cases')->where('user_id', $request->user()->id)->where('client_key', $request->input('client_key'))->first() : null;
$id = $path->create($request->user(), $request->all());
if ($existingReferenceCase) {
$selection = app(SectorResourceReferences::class)->selection($sector, $reference);
$oldPayload = app(VirtualDesk::class)->decode($existingReferenceCase->payload);
abort_unless(array_any($oldPayload['investor_path']['sector_references'] ?? [], fn ($row) => $row['key'] === $selection['key']), 409, 'A chave de criação já foi usada com outra referência sectorial; recupere o dossier existente.');
}
if ($source !== null) {
$case = $path->view($request->user(), $id)['case'];
app(LegacyContent::class)->attach($request->user(), $id, ['version' => $case['version'], 'resource_id' => (int) $source]);
@@ -88,7 +107,7 @@ class InvestorPathController extends Controller
if ($sector !== null) {
$case = $path->view($request->user(), $id)['case'];
app(SectorInvestment::class)->attach($request->user(), $id, ['version' => $case['version'], 'sector_slug' => $sector]);
app(SectorInvestment::class)->attach($request->user(), $id, ['version' => $case['version'], 'sector_slug' => $sector, 'sector_reference' => $reference]);
}
if ($zone) {
@@ -0,0 +1,30 @@
<?php
namespace App\Http\Controllers;
use App\Services\OpportunityCuration;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
class OpportunityCurationController extends Controller
{
public function show(Request $request, int $id, OpportunityCuration $service): Response
{
$base = str_starts_with($request->path(), 'minhas-oportunidades') ? '/minhas-oportunidades' : '/management/opportunities';
$filters = $request->validate(['page' => 'nullable|integer|min:1|max:1000000']);
return Inertia::render('management/opportunity-curation', [...$service->state($request->user(), $id, (int) ($filters['page'] ?? 1)), 'basePath' => $base]);
}
public function preview(Request $request, int $id, OpportunityCuration $service): JsonResponse
{
return response()->json($service->preview($request->user(), $id, $request->all()))->header('Cache-Control', 'private, no-store');
}
public function confirm(Request $request, int $id, OpportunityCuration $service): JsonResponse
{
return response()->json($service->confirm($request->user(), $id, $request->all()))->header('Cache-Control', 'private, no-store');
}
}
@@ -8,6 +8,7 @@ use App\Models\Sector;
use App\Services\InvestmentGeography;
use App\Services\OpportunityCanonicalContract;
use App\Services\OpportunityCanonicalResponse;
use App\Services\OpportunityCuration;
use App\Services\OpportunityManagement;
use App\Services\OpportunityManagementInstallation;
use App\Services\OpportunityMaturity;
@@ -130,6 +131,8 @@ class OpportunityManagementController extends Controller
$access->authorize($request->user(), 'view', $record);
}
Inertia::share(['basePath' => str_starts_with($request->path(), 'minhas-oportunidades') ? '/minhas-oportunidades' : '/management/opportunities',
'curationHref' => $record && app(OpportunityCuration::class)->allowed($request->user(), $record)
? (str_starts_with($request->path(), 'minhas-oportunidades') ? '/minhas-oportunidades/' : '/management/opportunities/').$record->id.'/destaque' : null,
'canonicalOptions' => ($record?->owner_user_id !== null || str_starts_with($request->path(), 'minhas-oportunidades')) ? app(OpportunityCanonicalContract::class)->options($request->user(), $record) : null,
'workflowState' => $record ? $access->state($record) : null,
'profileReauthenticationRequired' => $record && $record->owner_user_id !== null
@@ -0,0 +1,67 @@
<?php
namespace App\Http\Controllers;
use App\Models\Opportunity;
use App\Support\PublicSitemapXml;
use Generator;
use Symfony\Component\HttpFoundation\StreamedResponse;
class PublicSitemapController extends Controller
{
private const PAGE_SIZE = 1000;
public function index(PublicSitemapXml $xml): StreamedResponse
{
$base = $this->base();
$pages = (int) ceil(Opportunity::published()->count() / self::PAGE_SIZE);
$locations = (static function () use ($base, $pages): Generator {
yield $base.'/sitemap/pages.xml';
for ($page = 1; $page <= $pages; $page++) {
yield $base.'/sitemap/opportunities/'.$page.'.xml';
}
})();
return $this->response($xml, $locations, true);
}
public function pages(PublicSitemapXml $xml): StreamedResponse
{
$base = $this->base();
$paths = ['/', '/oportunidades', '/sectores', '/como-investir', '/documentos', '/apoio', '/simulador-fiscal', '/politica-de-privacidade', '/termos-de-uso'];
return $this->response($xml, array_map(fn ($path) => $base.$path, $paths));
}
public function opportunities(string $page, PublicSitemapXml $xml): StreamedResponse
{
$number = filter_var($page, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1, 'max_range' => 1000000]]);
abort_if($number === false, 404);
$base = $this->base();
$records = Opportunity::published()->orderBy('id')->offset(($number - 1) * self::PAGE_SIZE)->limit(self::PAGE_SIZE)->get(['slug']);
abort_if($records->isEmpty(), 404);
return $this->response($xml, $records->map(fn ($record) => $base.'/oportunidades/'.rawurlencode($record->slug)));
}
private function base(): string
{
$url = rtrim((string) config('app.url'), '/');
$parts = parse_url($url);
abort_unless(filter_var($url, FILTER_VALIDATE_URL) && is_array($parts)
&& in_array($parts['scheme'] ?? null, ['http', 'https'], true)
&& ! isset($parts['query']) && ! isset($parts['fragment']) && ! isset($parts['user']) && ! isset($parts['pass']), 503);
return $url;
}
/** @param iterable<string> $locations */
private function response(PublicSitemapXml $xml, iterable $locations, bool $index = false): StreamedResponse
{
return response()->stream(static function () use ($xml, $locations, $index): void {
foreach ($xml->chunks($locations, $index) as $chunk) {
echo $chunk;
}
}, 200, ['Content-Type' => 'application/xml; charset=UTF-8', 'Cache-Control' => 'no-store', 'X-Content-Type-Options' => 'nosniff']);
}
}
+18 -1
View File
@@ -78,6 +78,23 @@ class VirtualDeskController extends Controller
}
public function showCase(Request $request, string $id, VirtualDesk $desk): JsonResponse|Response
{
$data = $this->caseViewData($request, $id, $desk);
return $request->expectsJson() ? response()->json($data) : Inertia::render('desk/case', $data);
}
public function preDossier(Request $request, string $id, VirtualDesk $desk): \Illuminate\Http\Response
{
return response()->view('desk.pre-dossier', $this->caseViewData($request, $id, $desk), 200, [
'Cache-Control' => 'private, no-store', 'X-Content-Type-Options' => 'nosniff',
]);
}
/** Same authorised projection for the workspace and its preparation document.
* @return array<string,mixed>
*/
private function caseViewData(Request $request, string $id, VirtualDesk $desk): array
{
$case = $desk->case($request->user(), $id);
$owner = $desk->owns($request->user(), $case, false);
@@ -98,7 +115,7 @@ class VirtualDeskController extends Controller
'links' => DB::table('desk_institutional_links')->get()->map(fn ($link) => [...(array) $link, 'monitor' => app(InstitutionalLinkMonitor::class)->status($link)]), 'source' => config('virtual-desk.source'), 'support' => config('portal.support'),
'guidance' => app(InvestorGuidance::class)->installed() ? app(InvestorGuidance::class)->view($request->user(), $id) : null];
return $request->expectsJson() ? response()->json($data) : Inertia::render('desk/case', $data);
return $data;
}
/** @return Collection<int,\stdClass> */
+2
View File
@@ -81,6 +81,8 @@ class AppServiceProvider extends ServiceProvider
Cache::forget('portal_home_v2_en');
Cache::forget('portal_home_sector_organization_v1_pt');
Cache::forget('portal_home_sector_organization_v1_en');
Cache::forget('portal_home_territorial_v2_pt');
Cache::forget('portal_home_territorial_v2_en');
};
$model::saved($invalidate);
$model::deleted($invalidate);
+131
View File
@@ -6,10 +6,13 @@ use App\Models\LabourDemand;
use App\Models\TrainingCapacity;
use App\Models\User;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use JsonException;
final class EducationReviews
{
@@ -55,6 +58,134 @@ final class EducationReviews
});
}
/** Private preparation only; never a decision or an authority grant.
* @return array<string,mixed> */
public function preparation(User $actor, LabourDemand|TrainingCapacity $record): array
{
app(EducationAccess::class)->authorize($actor);
$event = $this->preparations($actor, $record)->orderByRaw("CASE WHEN (after_data->'education_preparation'->>'version') ~ '^[0-9]{1,10}$' THEN (after_data->'education_preparation'->>'version')::bigint END DESC NULLS LAST")->orderByDesc('occurred_at')->orderByDesc('id')->first(['after_data']);
$value = $event ? json_decode($event->after_data, true, 512, JSON_THROW_ON_ERROR)['education_preparation'] : null;
$snapshot = null;
$unavailable = false;
if ($value) {
try {
$decoded = json_decode(Crypt::decryptString($value['snapshot']), true, 512, JSON_THROW_ON_ERROR);
$snapshot = Validator::make(['snapshot' => $decoded], $this->preparationRules($decoded))->validate()['snapshot'];
} catch (DecryptException|JsonException|ValidationException) {
$unavailable = true;
}
}
// Empty reference maps remain JSON objects in the browser, like the declaration props.
if (is_array($snapshot)) {
if ($snapshot['base']['references'] === []) {
$snapshot['base']['references'] = (object) [];
}
if (is_array($snapshot['pending']) && $snapshot['pending']['base']['references'] === []) {
$snapshot['pending']['base']['references'] = (object) [];
}
}
return ['actor_id' => $actor->id, 'kind' => $record instanceof LabourDemand ? 'demands' : 'capacities', 'record_id' => $record->id, 'version' => (int) ($value['version'] ?? 0), 'operation_key' => $value['operation_key'] ?? null,
'snapshot' => $snapshot, 'unavailable' => $unavailable];
}
/** @param array<string,mixed> $input
* @return array<string,mixed> */
public function savePreparation(User $actor, string $kind, string $id, array $input): array
{
app(EducationAccess::class)->authorize($actor);
$model = match ($kind) {
'demands' => LabourDemand::class, 'capacities' => TrainingCapacity::class, default => abort(404)
};
$data = Validator::make($input, [...$this->preparationRules($input['snapshot'] ?? null),
'actor_id' => 'required|integer|min:1', 'version' => 'required|integer|min:0', 'operation_key' => 'required|uuid'])->validate();
abort_unless((int) $data['actor_id'] === $actor->id, 409, 'A conta mudou. Actualize a entrada antes de guardar a preparação.');
$key = strtolower($data['operation_key']);
$snapshot = $data['snapshot'];
foreach (['values', 'base'] as $field) {
$snapshot[$field]['version_no'] = (int) $snapshot[$field]['version_no'];
}
$snapshot['base']['quantity'] = (int) $snapshot['base']['quantity'];
foreach (['status', 'review_reason'] as $field) {
$snapshot['generation'][$field] = (int) $snapshot['generation'][$field];
}
if ($snapshot['pending'] !== null) {
$snapshot['pending']['values']['version_no'] = (int) $snapshot['pending']['values']['version_no'];
$snapshot['pending']['base']['version_no'] = (int) $snapshot['pending']['base']['version_no'];
$snapshot['pending']['base']['quantity'] = (int) $snapshot['pending']['base']['quantity'];
$snapshot['pending']['values']['client_key'] = strtolower($snapshot['pending']['values']['client_key']);
foreach (['reasonGeneration', 'statusGeneration'] as $field) {
$snapshot['pending'][$field] = (int) $snapshot['pending'][$field];
}
abort_unless($snapshot['pending']['values']['version_no'] === $snapshot['pending']['base']['version_no'], 422);
abort_unless($snapshot['pending']['reasonGeneration'] <= $snapshot['generation']['review_reason'] && $snapshot['pending']['statusGeneration'] <= $snapshot['generation']['status'], 422);
}
abort_unless($snapshot['values']['version_no'] === $snapshot['base']['version_no'], 422);
$hash = hash('sha256', json_encode(Arr::sortRecursive(['actor' => $actor->id, 'kind' => $kind, 'id' => $id,
'version' => (int) $data['version'], 'snapshot' => $snapshot]), JSON_THROW_ON_ERROR));
return DB::transaction(function () use ($actor, $model, $id, $data, $snapshot, $key, $hash): array {
$record = $model::lockForUpdate()->findOrFail($id);
$previous = $this->preparations($actor, $record)->whereRaw("after_data->'education_preparation'->>'operation_key' = ?", [$key])->first(['after_data']);
if ($previous) {
$receipt = json_decode($previous->after_data, true, 512, JSON_THROW_ON_ERROR)['education_preparation'];
abort_unless(hash_equals($receipt['hash'], $hash), 409, 'Esta tentativa de guardar já foi usada com outros dados.');
return $this->preparation($actor, $record);
}
$current = $this->preparation($actor, $record);
abort_unless($current['version'] === (int) $data['version'], 409, 'A preparação mudou noutra janela. Compare antes de substituir.');
abort_if($current['unavailable'], 409, 'A preparação anterior não pôde ser recuperada e foi preservada.');
abort_if($snapshot['base']['version_no'] > (int) $record->getAttribute('version_no'), 422, 'A versão analisada não existe nesta declaração.');
DB::table('audit_events')->insert(['source_system' => self::SOURCE, 'actor_user_id' => $actor->id,
'entity_type' => $record->getTable(), 'entity_id' => $id, 'action' => 'review_preparation_saved', 'data_classification' => 'restricted',
'after_data' => json_encode(['education_preparation' => ['version' => $current['version'] + 1, 'operation_key' => $key, 'hash' => $hash,
'snapshot' => Crypt::encryptString(json_encode($snapshot, JSON_THROW_ON_ERROR))]], JSON_THROW_ON_ERROR)]);
return $this->preparation($actor, $record);
});
}
private function preparations(User $actor, LabourDemand|TrainingCapacity $record): Builder
{
return DB::table('audit_events')->where('source_system', self::SOURCE)->where('actor_user_id', $actor->id)
->where('entity_type', $record->getTable())->where('entity_id', $record->id)->where('action', 'review_preparation_saved');
}
/** @return array<string,string> */
private function preparationRules(mixed $snapshot): array
{
$rules = ['snapshot' => 'required|array:values,generation,base,pending', 'snapshot.values' => 'required|array:version_no,status,review_reason',
'snapshot.generation' => 'required|array:status,review_reason', 'snapshot.pending' => 'present|nullable|array:values,reasonGeneration,statusGeneration,base'];
$roots = ['snapshot'];
if (is_array($snapshot) && is_array($snapshot['pending'] ?? null)) {
$roots[] = 'snapshot.pending';
$rules['snapshot.pending.values'] = 'required|array:version_no,status,review_reason,client_key';
$rules['snapshot.pending.values.client_key'] = 'required|uuid';
foreach (['reasonGeneration', 'statusGeneration'] as $field) {
$rules['snapshot.pending.'.$field] = 'required|integer|min:0|max:1000000000';
}
}
foreach (['status', 'review_reason'] as $field) {
$rules['snapshot.generation.'.$field] = 'required|integer|min:0|max:1000000000';
}
foreach ($roots as $root) {
$rules[$root.'.values.version_no'] = 'required|integer|min:1';
$rules[$root.'.values.status'] = 'required|in:reviewed,rejected';
$rules[$root.'.values.review_reason'] = 'present|string|max:3000';
$rules[$root.'.base'] = 'required|array:version_no,qualification,quantity,status,period_start,period_end,valid_until,source,reference_date,employer,references';
$rules[$root.'.base.version_no'] = 'required|integer|min:1';
$rules[$root.'.base.quantity'] = 'required|integer|min:0';
foreach (['qualification' => 250, 'status' => 40, 'period_start' => 40, 'period_end' => 40, 'valid_until' => 40, 'source' => 5000, 'reference_date' => 40, 'employer' => 1000] as $field => $max) {
$rules[$root.'.base.'.$field] = 'present|string|max:'.$max;
}
$rules[$root.'.base.references'] = 'present|array:occupation,skill,project,course,location';
$rules[$root.'.base.references.*'] = 'nullable|string|max:1000';
}
return $rules;
}
/** @return array{actor_id:int,commands:list<array{key:string,version:int}>} */
public function context(User $actor, LabourDemand|TrainingCapacity $record): array
{
+78
View File
@@ -0,0 +1,78 @@
<?php
namespace App\Services;
use Illuminate\Database\QueryException;
/** Combines independent, already-authorised sources without loading complete catalogues. */
final class FederatedSearch
{
/** @param array<string, callable(): array{count:int, fetch:callable(int,int): iterable}> $factories */
public function page(array $factories, int $requestedPage, int $size = 5, ?callable $score = null): array
{
$sources = [];
$unavailable = [];
foreach ($factories as $label => $factory) {
try {
$sources[$label] = $factory();
} catch (QueryException $exception) {
report($exception);
$unavailable[] = $label;
}
}
// If a source fails after its count, remove it and recompute the page offsets.
do {
$failed = false;
$total = array_sum(array_column($sources, 'count'));
$last = max(1, (int) ceil($total / $size));
$page = min(max(1, $requestedPage), $last);
$first = ($page - 1) * $size;
$items = [];
$positions = array_fill_keys(array_keys($sources), 0);
$buffers = array_fill_keys(array_keys($sources), []);
for ($position = 0; $position < min($total, $first + $size); $position++) {
$best = null;
$bestScore = PHP_INT_MIN;
foreach ($sources as $label => $source) {
if ($buffers[$label] === [] && $positions[$label] < $source['count']) {
try {
$batch = iterator_to_array((function () use ($source, $positions, $label, $size) {
yield from ($source['fetch'])($positions[$label], $size);
})(), false);
$positions[$label] += count($batch);
$buffers[$label] = $batch;
if ($batch === []) {
$positions[$label] = $source['count'];
}
} catch (QueryException $exception) {
report($exception);
$unavailable[] = $label;
unset($sources[$label]);
$failed = true;
break;
}
}
if ($buffers[$label] === []) {
continue;
}
$candidateScore = $score ? $score($buffers[$label][0]) : 0;
if ($best === null || $candidateScore > $bestScore) {
$best = $label;
$bestScore = $candidateScore;
}
}
if ($failed || $best === null) {
break;
}
$item = array_shift($buffers[$best]);
if ($position >= $first) {
$items[] = [...$item, 'category' => $best];
}
}
} while ($failed);
return ['data' => $items, 'total' => $total, 'current_page' => $page, 'last_page' => $last,
'per_page' => $size, 'unavailable' => $unavailable];
}
}
@@ -0,0 +1,97 @@
<?php
namespace App\Services;
use App\Models\User;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
/** A selected public funding reference in the existing encrypted investment dossier. */
final class FundingDossierPreparation
{
/** @return array<string,mixed> */
public function context(User $actor, string $id, ?string $opportunity): array
{
$case = app(VirtualDesk::class)->case($actor, $id);
abort_unless(app(VirtualDesk::class)->owns($actor, $case) || $actor->can('manage-portal'), 403);
$state = app(InvestorContext::class)->state($case);
$scope = app(InvestmentConsumerScope::class);
$opportunity = $scope->key($state, $opportunity, true);
$specific = $scope->context($state, $opportunity);
$selections = app(VirtualDesk::class)->decode($case->payload)['investor_path']['funding_selections'] ?? [];
return ['id' => $id, 'version' => (int) $case->version, 'closed' => $case->closed_at !== null, 'opportunity' => $opportunity,
'territory' => $specific['selection']['territory']['id'] ?? null,
'selections' => array_values(array_filter($selections, fn ($row) => ($row['opportunity'] ?? null) === $opportunity)),
'href' => '/investimentos/'.$id.($opportunity ? '?opportunity='.urlencode($opportunity) : '').'#financiamento'];
}
/** @param array<string,mixed> $input
* @return array<string,mixed> */
public function save(User $actor, string $programme, array $input): array
{
$data = Validator::make($input, ['context' => 'required|uuid', 'opportunity' => 'nullable|string|max:150', 'version' => 'required|integer|min:1',
'version_id' => 'required|uuid', 'product' => 'nullable|string|max:80', 'need' => ['required', Rule::in(array_keys(FundingInstitutionDetails::NEEDS))],
'territory' => 'nullable|uuid', 'location_id' => 'nullable|string|max:100', 'location_mode' => 'nullable|in:physical,coverage', 'operation_key' => 'required|uuid', 'consent' => 'accepted'])->validate();
$intent = hash('sha256', json_encode(Arr::sortRecursive([...$data, 'actor' => $actor->id, 'programme' => $programme]), JSON_THROW_ON_ERROR));
return DB::transaction(function () use ($actor, $programme, $data, $intent): array {
$desk = app(VirtualDesk::class);
$case = $desk->case($actor, $data['context'], true, true);
abort_if($case->closed_at, 409, 'O dossier está encerrado.');
$context = $this->context($actor, $case->id, $data['opportunity'] ?? null);
$payload = $desk->decode($case->payload);
$selections = $payload['investor_path']['funding_selections'] ?? [];
foreach ($selections as $selection) {
if ($selection['id'] === $data['operation_key']) {
abort_unless(hash_equals($selection['intent'], $intent), 409, 'A intenção já tem outro conteúdo.');
return $context;
}
}
foreach ($selections as $selection) {
if (($selection['opportunity'] ?? null) === $context['opportunity'] && $selection['programme_id'] === $programme && $selection['version_id'] === $data['version_id'] && ($selection['product']['code'] ?? null) === ($data['product'] ?? null) && $selection['need'] === $data['need'] && ($selection['territory'] ?? null) === ($data['territory'] ?? null) && ($selection['location']['id'] ?? null) === ($data['location_id'] ?? null)) {
return $context;
}
}
abort_unless((int) $case->version === (int) $data['version'], 409, 'O dossier mudou. Consulte-o antes de guardar esta escolha.');
$catalogue = app(FundingProgrammes::class);
$record = $catalogue->published($programme);
abort_unless($record['version_id'] === $data['version_id'], 409, 'A ficha pública mudou. Confira a versão actual.');
if (! empty($data['territory'])) {
abort_unless(app(InvestmentGeography::class)->publicReferences()->where('id', $data['territory'])->exists(), 422);
}
$location = null;
if (! empty($data['location_id'])) {
foreach ($record['payload']['locations'] ?? [] as $candidate) {
if (($candidate['id'] ?? null) === $data['location_id']) {
$location = $candidate;
}
}
abort_unless($location, 422, 'O local seleccionado não pertence à versão consultada da instituição.');
}
$product = null;
if ($record['payload']['kind'] === 'institution') {
foreach ($record['payload']['products'] as $candidate) {
if (($candidate['code'] ?? null) === ($data['product'] ?? null)) {
$product = $candidate;
break;
}
}
abort_unless(($record['payload']['products'] === [] && empty($data['product']) && app(FundingInstitutionClassification::class)->permitsUnknownProduct($record['payload'], $data['need'])) || ($product && app(FundingInstitutionDetails::class)->match($product, $data)), 422, 'O serviço ou produto escolhido não corresponde à informação da ficha para esta necessidade/local. Confira as condições ou seleccione outra referência.');
}
abort_if(count($selections) >= 200, 422, 'O dossier atingiu o limite de referências de financiamento.');
$selection = ['id' => $data['operation_key'], 'intent' => $intent, 'actor' => $actor->id, 'opportunity' => $context['opportunity'],
'programme_id' => $programme, 'version_id' => $record['version_id'], 'version' => $record['version'], 'title' => $record['payload']['title'],
'record' => $record['payload'], 'product' => $product, 'need' => $data['need'], 'territory' => $data['territory'] ?? null, 'location' => $location, 'location_mode' => $data['location_mode'] ?? 'coverage', 'categories' => $record['payload']['institution_categories'] ?? [], 'institution_types' => $record['payload']['institution_types'] ?? [], 'institution_key' => $record['payload']['institution_key'] ?? null, 'recorded_at' => now()->toIso8601String(),
'notice' => 'Referência para preparação. Não é candidatura, contacto enviado, aprovação ou garantia de financiamento.'];
$payload['investor_path']['funding_selections'][] = $selection;
DB::table('desk_cases')->where('id', $case->id)->update(['payload' => $desk->encode($payload), 'version' => (int) $case->version + 1, 'updated_at' => now()]);
$desk->event($case, $actor, 'funding_preparation_saved', ['selection_id' => $selection['id'], 'programme_id' => $programme, 'version_id' => $record['version_id'], 'opportunity' => $context['opportunity']]);
return $this->context($actor, $case->id, $context['opportunity']);
});
}
}
@@ -0,0 +1,73 @@
<?php
namespace App\Services;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
/** Documentary classification and physical presence; neither is lending eligibility. */
final class FundingInstitutionClassification
{
public const CATEGORIES = ['banking' => 'Banca', 'credit' => 'Crédito e microfinanças', 'investment' => 'Gestão de investimento', 'pensions' => 'Fundos de pensões', 'insurance' => 'Seguros e resseguros', 'intermediation' => 'Mediação de seguros', 'exchange' => 'Câmbio', 'payments' => 'Pagamentos e serviços interbancários', 'supervision' => 'Supervisão', 'other' => 'Outras instituições'];
public const TYPES = ['commercial_bank' => 'Banco comercial', 'investment_bank' => 'Banco de investimento', 'bank' => 'Banco (tipo não especificado)', 'microbank' => 'Microbanco', 'cooperative' => 'Cooperativa de crédito', 'savings' => 'Organização de poupança e empréstimo', 'investment_manager' => 'Gestora de fundos de investimento', 'investment_company' => 'Sociedade de investimento', 'pension_manager' => 'Gestora de fundos de pensões', 'insurer' => 'Seguradora', 'reinsurer' => 'Resseguradora', 'microinsurer' => 'Micro-seguradora', 'broker' => 'Corretora de seguros', 'agent' => 'Agente de seguros', 'reinsurance_broker' => 'Corretor de resseguro', 'exchange_house' => 'Casa de câmbio', 'electronic_money' => 'Instituição de moeda electrónica', 'interbank' => 'Serviços interbancários', 'supervisor' => 'Supervisor', 'other' => 'Outro tipo documentado'];
/** @param array<string,mixed> $input
* @return array<string,mixed> */
public function normalize(array $input): array
{
return Validator::make($input, [
'institution_categories' => 'array|max:10', 'institution_categories.*' => ['required', 'distinct', Rule::in(array_keys(self::CATEGORIES))],
'institution_types' => 'array|max:10', 'institution_types.*' => ['required', 'distinct', Rule::in(array_keys(self::TYPES))],
'institution_services' => 'array|max:12', 'institution_services.*' => ['required', 'distinct', Rule::in(array_keys(FundingInstitutionDetails::NEEDS))],
'institution_status' => 'nullable|in:reported,historical,merged,unknown', 'successor_key' => 'nullable|string|max:100',
'register_reference' => 'nullable|string|max:2000', 'aliases' => 'array|max:100', 'aliases.*' => 'required|string|max:500',
'source_rows' => 'array|max:250', 'source_rows.*' => 'required|array:id,section,text', 'source_rows.*.id' => 'required|string|max:100',
'source_rows.*.section' => 'required|string|max:250', 'source_rows.*.text' => 'required|string|max:8000',
])->validate();
}
/** @param array<string,mixed> $filters */
public function filter(Builder $query, array $filters): void
{
foreach (['category' => 'institution_categories', 'institution_type' => 'institution_types'] as $key => $field) {
if (! empty($filters[$key])) {
$query->whereRaw('v.payload->? @> ?::jsonb', [$field, json_encode([$filters[$key]], JSON_THROW_ON_ERROR)]);
}
}
if (empty($filters['include_historical'])) {
$query->whereRaw("COALESCE(v.payload->>'institution_status','reported') NOT IN ('historical','merged')");
}
if (($filters['location_mode'] ?? 'coverage') !== 'physical') {
return;
}
$query->whereRaw("v.payload->>'kind'='institution'");
if (empty($filters['territory'])) {
return;
}
$unknown = ($filters['certainty'] ?? 'include_unknown') === 'include_unknown';
$path = array_column(app(InvestmentGeography::class)->path($filters['territory']), 'id');
$clauses = ["l->'territory_path' @> ?::jsonb"];
$bindings = [json_encode([['id' => $filters['territory']]], JSON_THROW_ON_ERROR)];
if ($unknown) {
foreach ($path as $id) {
$clauses[] = "l->>'territory_id' = ?";
$bindings[] = $id;
}
}
$status = $unknown ? '' : " AND COALESCE(l->>'evidence_status','reported') NOT IN ('to_confirm','historical','conflict','unlocated')";
$query->where(function (Builder $q) use ($clauses, $bindings, $status, $unknown): void {
$q->whereRaw("EXISTS (SELECT 1 FROM jsonb_array_elements(COALESCE(v.payload->'locations','[]'::jsonb)) l WHERE (".implode(' OR ', $clauses).')'.$status.')', $bindings);
if ($unknown) {
$q->orWhereRaw("NOT EXISTS (SELECT 1 FROM jsonb_array_elements(COALESCE(v.payload->'locations','[]'::jsonb)) l WHERE NULLIF(l->>'territory_id','') IS NOT NULL)");
}
});
}
/** @param array<string,mixed> $payload */
public function permitsUnknownProduct(array $payload, ?string $need): bool
{
return ! $need || ! isset($payload['institution_services']) || in_array($need, $payload['institution_services'], true);
}
}
+202
View File
@@ -0,0 +1,202 @@
<?php
namespace App\Services;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
/** Additional factual fields in the existing versioned funding catalogue. */
final class FundingInstitutionDetails
{
public const NATURES = ['bank' => 'Banco', 'credit' => 'Instituição de crédito', 'microfinance' => 'Microfinanças', 'investment_fund' => 'Fundo de investimento', 'development' => 'Agência de desenvolvimento/financiamento', 'other' => 'Outra natureza documentada', 'unknown' => 'Natureza por confirmar'];
public const NEEDS = ['investment' => 'Investimento/equipamento', 'working_capital' => 'Fundo de maneio', 'equity' => 'Participação de capital', 'guarantee' => 'Garantia', 'technical_assistance' => 'Assistência técnica', 'insurance' => 'Seguros', 'foreign_exchange' => 'Câmbio', 'payments' => 'Pagamentos', 'pension' => 'Pensões', 'investment_management' => 'Gestão de investimento', 'other' => 'Outra necessidade documentada'];
/** @param array<string,mixed> $input
* @return array<string,mixed> */
public function normalize(array $input, bool $publishing): array
{
$required = $publishing ? 'required' : 'nullable';
$rules = [
'institution_key' => 'required|string|regex:/^[a-z0-9][a-z0-9.-]{0,99}$/D',
'institution_nature' => ['required', Rule::in(array_keys(self::NATURES))],
'contacts' => 'array|max:100', 'locations' => 'array|max:100', 'products' => 'array|max:40',
'contacts.*' => 'array:id,kind,value,source_url,source_reference,checked_on,source_document_name,source_document_sha256,evidence_status,origin,source_refs',
'contacts.*.kind' => 'required|in:website,email,phone,address', 'contacts.*.value' => "$required|string|max:1000",
'locations.*' => 'array:id,role,label,address,territory_id,source_url,source_reference,checked_on,source_document_name,source_document_sha256,evidence_status,origin,source_refs,location_text,hours',
'locations.*.role' => 'required|in:headquarters,branch,contact_point', 'locations.*.label' => "$required|string|max:250",
'locations.*.address' => 'nullable|string|max:1500', 'locations.*.territory_id' => 'nullable|uuid',
'products.*' => 'array:code,title,instrument,needs,coverage,coverage_text,territory_ids,conditions,amounts,availability,contact_url,source_url,source_reference,checked_on,source_document_name,source_document_sha256',
'products.*.code' => 'required|string|regex:/^[a-z0-9][a-z0-9-]{0,79}$/D|distinct', 'products.*.title' => "$required|string|max:250",
'products.*.instrument' => 'nullable|string|max:250', 'products.*.needs' => 'array|max:6',
'products.*.needs.*' => ['required', 'distinct', Rule::in(array_keys(self::NEEDS))],
'products.*.coverage' => 'required|in:national,territorial,unknown', 'products.*.coverage_text' => 'nullable|string|max:2000',
'products.*.territory_ids' => 'array|max:30', 'products.*.territory_ids.*' => 'required|uuid|distinct',
'products.*.conditions' => 'nullable|string|max:4000', 'products.*.amounts' => 'nullable|string|max:2000',
'products.*.availability' => 'required|in:documented,unknown,closed', 'products.*.contact_url' => 'nullable|url:http,https|max:2000',
];
foreach (['contacts', 'locations', 'products'] as $prefix) {
$rules[$prefix.'.*.source_url'] = 'nullable|url:http,https|max:2000';
$rules[$prefix.'.*.source_document_name'] = 'nullable|string|max:250';
$rules[$prefix.'.*.source_document_sha256'] = 'nullable|string|regex:/^[a-f0-9]{64}$/D';
$rules[$prefix.'.*.source_reference'] = "$required|string|max:1500";
$rules[$prefix.'.*.checked_on'] = "$required|date_format:Y-m-d|before_or_equal:today";
}
foreach (['contacts', 'locations'] as $prefix) {
$rules[$prefix.'.*.id'] = 'nullable|string|max:100';
$rules[$prefix.'.*.evidence_status'] = 'nullable|in:reported,to_confirm,historical,conflict,unlocated';
$rules[$prefix.'.*.origin'] = 'nullable|string|max:500';
$rules[$prefix.'.*.source_refs'] = 'array|max:100';
$rules[$prefix.'.*.source_refs.*'] = 'required|string|max:100';
}
$rules['locations.*.location_text'] = 'nullable|string|max:1000';
$rules['locations.*.hours'] = 'nullable|string|max:1000';
$data = [...Validator::make($input, $rules)->validate(), ...app(FundingInstitutionClassification::class)->normalize($input)];
if ($publishing) {
foreach (['contacts', 'locations', 'products'] as $prefix) {
foreach ($data[$prefix] ?? [] as $item) {
Validator::make($item, ['source_url' => empty($item['source_document_sha256']) ? 'required|url:http,https' : 'nullable|url:http,https', 'source_document_name' => empty($item['source_url']) ? 'required|string' : 'nullable|string'])->validate();
}
}
}
foreach (['contacts', 'locations', 'products'] as $key) {
$data[$key] ??= [];
}
foreach ($data['contacts'] as $contact) {
Validator::make(['value' => $contact['value'] ?? null], ['value' => match ($contact['kind']) {
'website' => "$required|url:http,https|max:1000",
'email' => "$required|email|max:250",
default => "$required|string|max:1000",
}])->validate();
}
foreach ($data['locations'] as &$location) {
if (! empty($location['territory_id'])) {
$this->territory($location['territory_id']);
if ($publishing) {
$location['territory_path'] = $this->path($location['territory_id']);
}
}
}
unset($location);
foreach ($data['products'] as &$product) {
$product['needs'] ??= [];
$product['territory_ids'] ??= [];
Validator::make($product, ['territory_ids' => $product['coverage'] === 'territorial' ? ($publishing ? 'required|array|min:1' : 'array') : 'array|size:0'])->validate();
// Territorial drafts may be incomplete; other coverage modes never inherit the headquarters.
foreach ($product['territory_ids'] as $id) {
$this->territory($id);
}
if ($publishing) {
$product['territory_paths'] = array_map($this->path(...), $product['territory_ids']);
}
}
unset($product);
return $data;
}
private function territory(string $id): void
{
Validator::make(['territory' => $id], ['territory' => [Rule::in(app(InvestmentGeography::class)->publicReferences()->pluck('id')->all())]])->validate();
}
/** @return list<array{id:string,name:string,type:string}> */
private function path(string $id): array
{
return array_map(fn ($node) => ['id' => $node->id, 'name' => $node->name, 'type' => $node->location_type], app(InvestmentGeography::class)->path($id));
}
/** Same product must satisfy both location and need; branch addresses never imply lending coverage.
* @param array<string,mixed> $product
* @param array<string,mixed> $filters */
public function match(array $product, array $filters): bool
{
$unknown = ($filters['certainty'] ?? 'include_unknown') === 'include_unknown';
if ($product['availability'] === 'closed') {
return false;
}
if (! $unknown && $product['availability'] === 'unknown') {
return false;
}
if (! empty($filters['need']) && ! in_array($filters['need'], $product['needs'], true) && ! ($unknown && $product['needs'] === [])) {
return false;
}
if (empty($filters['territory'])) {
return true;
}
if ($product['coverage'] === 'national') {
return true;
}
if ($product['coverage'] === 'unknown') {
return $unknown;
}
$path = array_column(app(InvestmentGeography::class)->path($filters['territory']), 'id');
foreach ($product['territory_paths'] ?? [] as $covered) {
$ids = array_column($covered, 'id');
if (array_intersect($path, $product['territory_ids']) !== [] || in_array($filters['territory'], $ids, true)) {
return true;
}
}
return false;
}
/** @param array<string,mixed> $filters */
public function applyFilters(Builder $query, array $filters): void
{
app(FundingInstitutionClassification::class)->filter($query, $filters);
if (empty($filters['need']) && (empty($filters['territory']) || ($filters['location_mode'] ?? '') === 'physical')) {
return;
}
$unknown = ($filters['certainty'] ?? 'include_unknown') === 'include_unknown';
$conditions = ["p->>'availability' <> 'closed'"];
$bindings = [];
if (! $unknown) {
$conditions[] = "p->>'availability' <> 'unknown'";
}
if (! empty($filters['need'])) {
$conditions[] = "(p->'needs' @> ?::jsonb".($unknown ? " OR jsonb_array_length(p->'needs')=0" : '').')';
$bindings[] = json_encode([$filters['need']], JSON_THROW_ON_ERROR);
}
if (! empty($filters['territory']) && ($filters['location_mode'] ?? '') !== 'physical') {
$path = array_column(app(InvestmentGeography::class)->path($filters['territory']), 'id');
$parts = ["p->>'coverage'='national'", "EXISTS (SELECT 1 FROM jsonb_array_elements(COALESCE(p->'territory_paths','[]'::jsonb)) t WHERE t @> ?::jsonb)"];
$bindings[] = json_encode([['id' => $filters['territory']]], JSON_THROW_ON_ERROR);
foreach ($path as $id) {
$parts[] = "p->'territory_ids' @> ?::jsonb";
$bindings[] = json_encode([$id], JSON_THROW_ON_ERROR);
}
if ($unknown) {
$parts[] = "p->>'coverage'='unknown'";
}
$conditions[] = '('.implode(' OR ', $parts).')';
}
$sql = "EXISTS (SELECT 1 FROM jsonb_array_elements(COALESCE(v.payload->'products','[]'::jsonb)) p WHERE ".implode(' AND ', $conditions).')';
$query->where(function (Builder $q) use ($sql, $bindings, $unknown, $filters): void {
$q->where(function (Builder $q) use ($unknown, $filters): void {
$q->whereRaw("v.payload->>'kind' <> 'institution'");
if (! empty($filters['need'])) {
$q->where(function (Builder $q) use ($filters, $unknown): void {
$q->whereRaw("v.payload->'funding_needs' @> ?::jsonb", [json_encode([$filters['need']], JSON_THROW_ON_ERROR)]);
if ($unknown) {
$q->orWhereRaw("jsonb_array_length(COALESCE(v.payload->'funding_needs','[]'::jsonb))=0");
}
});
}
})->orWhere(function (Builder $q) use ($sql, $bindings, $unknown, $filters): void {
$q->whereRaw("v.payload->>'kind' = 'institution'")->where(function (Builder $q) use ($sql, $bindings, $unknown, $filters): void {
$q->whereRaw($sql, $bindings);
if ($unknown) {
$q->orWhere(function (Builder $p) use ($filters): void {
$p->whereRaw("jsonb_array_length(COALESCE(v.payload->'products','[]'::jsonb))=0");
if (! empty($filters['need'])) {
$p->where(fn ($z) => $z->whereRaw("v.payload->'institution_services' @> ?::jsonb", [json_encode([$filters['need']], JSON_THROW_ON_ERROR)])->orWhereRaw("NOT jsonb_exists(v.payload, 'institution_services')"));
}
});
}
});
});
});
}
}
+81 -7
View File
@@ -6,26 +6,100 @@ use App\Models\User;
use App\Support\CheckedFile;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
/** Packaged official references prepare private drafts through existing editorial authority. */
/** Packaged documentary references prepare private drafts through existing editorial authority. */
final class FundingProgrammeReferences
{
/** @return array{references:array<string,mixed>,sha256:string} */
private function package(): array
{
$references = [];
$hashes = [];
foreach (['funding-references.json', 'funding-institutions-nov2026.json'] as $file) {
$bytes = CheckedFile::read(database_path('data/'.$file));
$package = json_decode($bytes, true, 512, JSON_THROW_ON_ERROR);
abort_unless($package['format'] === 'investmoz-funding-references-v1', 503);
$references = [...$references, ...$package['references']];
$hashes[$file] = hash('sha256', $bytes);
}
return ['references' => $references, 'sha256' => hash('sha256', json_encode($hashes, JSON_THROW_ON_ERROR))];
}
/** @return array<string,mixed> */
public function preview(User $actor, string $code): array
{
app(FundingProgrammes::class)->guard($actor);
$bytes = CheckedFile::read(database_path('data/funding-references.json'));
$package = json_decode($bytes, true, 512, JSON_THROW_ON_ERROR);
abort_unless($package['format'] === 'investmoz-funding-references-v1', 503);
$package = $this->package();
$reference = $package['references'][$code] ?? null;
abort_unless(is_array($reference), 404);
$plan = ['code' => $code, 'edition' => $reference['edition'], 'draft' => $reference['draft'], 'provenance' => $reference['provenance'],
'package_sha256' => hash('sha256', $bytes)];
$unresolved = [];
$resolve = function (string $name) use (&$unresolved): ?string {
$matches = app(InvestmentGeography::class)->publicReferences()->where('location_type', 'province')->get(['id', 'name'])->filter(fn ($row) => Str::lower(Str::ascii($row->name)) === Str::lower(Str::ascii($name)));
if ($matches->count() !== 1) {
$unresolved[] = $name;
return null;
}
return $matches->first()->id;
};
if (! empty($reference['territory_names'])) {
$ids = array_values(array_filter(array_map($resolve, $reference['territory_names'])));
if (count($ids) === count($reference['territory_names'])) {
$reference['draft']['territory_ids'] = $ids;
} else {
$reference['draft']['coverage'] = 'unknown';
$reference['draft']['territory_ids'] = [];
$reference['draft']['coverage_text'] .= ' Correspondência ao cadastro territorial por confirmar.';
}
}
foreach ($reference['location_territory_names'] ?? [] as $index => $name) {
$reference['draft']['locations'][$index]['territory_id'] = $resolve($name);
if (! $reference['draft']['locations'][$index]['territory_id']) {
$reference['draft']['locations'][$index]['evidence_status'] = 'unlocated';
}
}
$plan = ['unresolved_territories' => $unresolved, 'code' => $code, 'edition' => $reference['edition'], 'draft' => $reference['draft'], 'provenance' => $reference['provenance'],
'package_sha256' => $package['sha256']];
$plan['hash'] = hash_hmac('sha256', json_encode(Arr::sortRecursive($plan), JSON_THROW_ON_ERROR), config('app.key'));
return $plan;
}
/** @return list<array{code:string,title:string}> */
public function choices(User $actor): array
{
app(FundingProgrammes::class)->guard($actor);
$package = $this->package();
$choices = [];
foreach ($package['references'] as $code => $reference) {
$choices[] = ['code' => $code, 'title' => $reference['draft']['title']];
}
return $choices;
}
/** @return array<string,mixed> */
public function assisted(User $actor): array
{
app(FundingProgrammes::class)->guard($actor);
$package = $this->package();
$existing = DB::table('funding_programmes')->get(['id', 'code', 'version', 'published_version_id'])->keyBy('code');
$items = [];
foreach ($package['references'] as $code => $reference) {
$draft = $reference['draft'];
$row = $existing->get($code);
$items[] = ['code' => $code, 'title' => $draft['title'], 'summary' => $draft['summary'], 'categories' => $draft['institution_categories'] ?? [],
'types' => $draft['institution_types'] ?? [], 'status' => $draft['institution_status'] ?? 'reported', 'source_document' => $draft['source_document_name'] ?? null,
'source_sha256' => $draft['source_document_sha256'] ?? null, 'locations' => $draft['locations'] ?? [], 'contacts' => $draft['contacts'] ?? [],
'id' => $row?->id, 'published' => $row?->published_version_id !== null];
}
return ['dataset_hash' => $package['sha256'], 'editor_id' => $actor->id, 'items' => $items];
}
/** @return array{id:string,created:bool} */
public function prepare(User $actor, string $code, string $hash): array
{
@@ -39,7 +113,7 @@ final class FundingProgrammeReferences
return ['id' => $existing->id, 'created' => false];
}
$id = app(FundingProgrammes::class)->save($actor, null, ['version' => 0, 'code' => $code,
'reason' => 'Preparação de referência oficial '.$plan['edition'].'; rascunho sem publicação automática.', 'draft' => $plan['draft']]);
'reason' => 'Preparação de referência documental '.$plan['edition'].'; rascunho sem publicação automática.', 'draft' => $plan['draft']]);
DB::table('audit_events')->insert(['source_system' => 'funding-catalogue', 'entity_type' => 'funding_programmes', 'entity_id' => $id,
'actor_user_id' => $actor->id, 'action' => 'reference_prepared', 'data_classification' => 'restricted',
'after_data' => json_encode(['edition' => $plan['edition'], 'package_sha256' => $plan['package_sha256'], 'provenance' => $plan['provenance'], 'effect' => 'private_draft_only'], JSON_THROW_ON_ERROR)]);
+41 -4
View File
@@ -18,7 +18,7 @@ use Symfony\Component\HttpFoundation\BinaryFileResponse;
/** Informational funding catalogue, separate from applications, credit decisions and fiscal calculations. */
final class FundingProgrammes
{
public const KINDS = ['fund' => 'Fundo', 'programme' => 'Programa de apoio', 'incentive' => 'Incentivo'];
public const KINDS = ['institution' => 'Instituição financeira', 'fund' => 'Fundo', 'programme' => 'Programa de apoio', 'incentive' => 'Incentivo'];
public const WINDOWS = ['dated' => 'Datas documentadas', 'rolling' => 'Recepção contínua documentada', 'unknown' => 'Janela por confirmar', 'closed' => 'Encerrada segundo a fonte'];
@@ -38,6 +38,19 @@ final class FundingProgrammes
abort_unless($this->access($actor), 403);
}
/** @param array<string,mixed> $reference */
public function matchesReference(User $actor, string $id, array $reference): bool
{
$this->guard($actor);
$row = DB::table('funding_programmes')->where('id', $id)->firstOrFail();
$expected = $this->normalize($actor, $reference, false);
$actual = json_decode($row->draft, true, 512, JSON_THROW_ON_ERROR);
abort_unless(is_array($actual), 503);
return json_encode(Arr::sortRecursive($actual), JSON_THROW_ON_ERROR)
=== json_encode(Arr::sortRecursive($expected), JSON_THROW_ON_ERROR);
}
public function publicQuery(): Builder
{
abort_unless($this->installed(), 404);
@@ -56,6 +69,7 @@ final class FundingProgrammes
public function catalogueQuery(array $filters): Builder
{
$query = $this->publicQuery();
app(FundingInstitutionDetails::class)->applyFilters($query, $filters);
$includeUnknown = ($filters['certainty'] ?? 'include_unknown') === 'include_unknown';
if (! empty($filters['sector'])) {
$query->where(function (Builder $scope) use ($filters, $includeUnknown): void {
@@ -66,10 +80,10 @@ final class FundingProgrammes
}
});
}
if (! empty($filters['territory'])) {
if (! empty($filters['territory']) && ($filters['location_mode'] ?? '') !== 'physical') {
$path = array_column(app(InvestmentGeography::class)->path($filters['territory']), 'id');
$query->where(function (Builder $scope) use ($filters, $path, $includeUnknown): void {
$scope->whereRaw("v.payload->>'coverage' = 'national'")
$scope->whereRaw("v.payload->>'kind' = 'institution'")->orWhereRaw("v.payload->>'coverage' = 'national'")
->orWhereJsonContains('v.payload->territory_ancestor_ids', $filters['territory']);
foreach ($path as $id) {
$scope->orWhereJsonContains('v.payload->territory_ids', $id);
@@ -102,6 +116,15 @@ final class FundingProgrammes
public function filterExplanation(array $record, array $filters): array
{
$payload = $record['payload'];
if ($payload['kind'] === 'institution') {
$products = array_values(array_filter($payload['products'] ?? [], fn ($p) => app(FundingInstitutionDetails::class)->match($p, $filters)));
if (($filters['location_mode'] ?? '') === 'physical') {
return ['institution' => 'Pesquisa por presença física documentada, não por cobertura de financiamento. Confira a precisão do local e as indicações por confirmar na ficha.'];
}
return ['institution' => $products === [] ? 'Instituição identificada; produtos, cobertura e disponibilidade por confirmar. Não é uma correspondência confirmada.' : 'Consulte os produtos correspondentes e as condições por confirmar. Sede e balcões não determinam a cobertura do financiamento.'];
}
$result = [];
if (! empty($filters['sector'])) {
$result['sector'] = $payload['sectors_scope'] === 'unknown'
@@ -200,6 +223,13 @@ final class FundingProgrammes
if (! $row && DB::table('funding_programmes')->where('code', $data['code'])->exists()) {
throw ValidationException::withMessages(['code' => 'Este identificador já pertence a uma ficha. Abra-a para actualizar ou acrescentar uma janela.']);
}
if ($draft['kind'] === 'institution') {
DB::select('select pg_advisory_xact_lock(261102, hashtext(?))', ['institution:'.$draft['institution_key']]);
$duplicate = DB::table('funding_programmes')->whereRaw("draft->>'kind' = 'institution'")->whereRaw("draft->>'institution_key' = ?", [$draft['institution_key']])->when($id, fn ($q) => $q->where('id', '<>', $id))->exists();
if ($duplicate) {
throw ValidationException::withMessages(['institution_key' => 'A instituição já tem ficha. Reutilize a identidade existente; não substitua as suas edições.']);
}
}
$id ??= (string) Str::uuid();
DB::table('funding_programmes')->updateOrInsert(['id' => $id], ['code' => $data['code'], 'version' => (int) $data['version'] + 1,
'draft' => json_encode($draft, JSON_THROW_ON_ERROR), 'created_by' => $row->created_by ?? $actor->id,
@@ -218,13 +248,14 @@ final class FundingProgrammes
$rules = [
'title' => "$required|string|max:250", 'kind' => ['required', Rule::in(array_keys(self::KINDS))], 'summary' => "$required|string|max:4000",
'manager_name' => 'nullable|string|max:250', 'manager_organization_id' => ['nullable', 'uuid', Rule::exists('organizations', 'id')->whereNull('deleted_at')],
'source_url' => "$required|url:http,https|max:2000", 'source_reference' => "$required|string|max:1500", 'source_checked_on' => "$required|date_format:Y-m-d|before_or_equal:today",
'source_url' => 'nullable|url:http,https|max:2000', 'source_document_name' => 'nullable|string|max:250', 'source_document_sha256' => 'nullable|string|regex:/^[a-f0-9]{64}$/D', 'source_reference' => "$required|string|max:1500", 'source_checked_on' => "$required|date_format:Y-m-d|before_or_equal:today",
'source_published_on' => 'nullable|date_format:Y-m-d', 'source_status' => 'required|in:identified,unknown,example',
'coverage' => 'required|in:national,territorial,unknown', 'coverage_text' => 'nullable|string|max:2000',
'territory_ids' => 'array|max:30', 'territory_ids.*' => 'required|uuid|distinct',
'sectors_scope' => 'required|in:all,selected,unknown', 'sectors_text' => 'nullable|string|max:2000', 'sector_ids' => 'array|max:40',
'sector_ids.*' => ['required', 'integer', 'distinct', Rule::in(Sector::choices()->whereNotNull('published_at')->pluck('id')->all())],
'resource_ids' => 'array|max:30', 'resource_ids.*' => 'required|integer|distinct|exists:portal_resources,id',
'funding_needs' => 'array|max:6', 'funding_needs.*' => ['required', 'distinct', Rule::in(array_keys(FundingInstitutionDetails::NEEDS))],
'criteria' => 'array|max:30', 'windows' => 'array|max:20',
'windows.*' => 'required|array:code,title,mode,start_on,end_on,coverage,coverage_text,territory_ids,source_url,source_reference,checked_on,application_url,criteria',
'windows.*.code' => 'required|string|regex:/^[a-z0-9][a-z0-9-]{0,79}$/D|distinct', 'windows.*.title' => "$required|string|max:250",
@@ -244,6 +275,12 @@ final class FundingProgrammes
$rules[$prefix.'.*.explanation'] = "$required|string|max:2000";
}
$data = Validator::make($input, $rules)->validate();
if ($publishing && empty($data['source_url'])) {
Validator::make($data, ['source_document_name' => 'required|string', 'source_document_sha256' => 'required|string|size:64'])->validate();
}
if ($data['kind'] === 'institution') {
$data = [...$data, ...app(FundingInstitutionDetails::class)->normalize($input, $publishing)];
}
$data = [...$data, 'territory_ids' => $data['territory_ids'] ?? [], 'sector_ids' => $data['sector_ids'] ?? [], 'resource_ids' => $data['resource_ids'] ?? [],
'criteria' => $data['criteria'] ?? [], 'windows' => $data['windows'] ?? []];
if ($data['coverage'] !== 'territorial' && $data['territory_ids'] !== []) {
+30 -6
View File
@@ -260,26 +260,41 @@ class InvestmentCatalogue
/** Public result presentation only; no editorial or territorial data is saved. */
public function presentLocation(Opportunity $record): Opportunity
{
$record->setAttribute('territorial_summary', $this->locationPresentation($record)['summary']);
return $record;
}
/** @return array{summary: string, provinces: list<string>} */
public function locationPresentation(Opportunity $record): array
{
$geo = app(InvestmentGeography::class);
$paths = [];
$provinceNames = [];
$ids = array_filter([$record->primary_territory_id, ...$record->additionalTerritories()->pluck('geo_locations.id')->all()]);
foreach (array_unique($ids) as $id) {
$path = $geo->path($id);
if ($path) {
$paths[] = implode(' / ', array_column($path, 'name'));
foreach ($path as $node) {
if ($node->location_type === 'province') {
$provinceNames[] = (string) $node->name;
}
}
}
}
if ($paths) {
$label = implode('; ', array_unique($paths));
} elseif ($provinces = app(InvestmentCorridorContext::class)->catalogueProvinces($record)) {
$label = implode(' / ', array_column($provinces, 'name')).' — contexto provincial do corredor; distrito e posto administrativo não indicados';
$provinceNames = array_map(fn ($province) => (string) $province->name, $provinces);
$label = implode(' / ', $provinceNames).' — contexto provincial do corredor; distrito e posto administrativo não indicados';
} else {
$label = implode(' / ', array_filter([$record->province, $record->district])) ?: 'Localização não indicada';
$provinceNames = $record->province ? [(string) $record->province] : [];
}
$record->setAttribute('territorial_summary', $label);
return $record;
return ['summary' => $label, 'provinces' => array_values(array_unique($provinceNames))];
}
/** @param array<string,mixed> $territoryFilters
@@ -507,11 +522,11 @@ class InvestmentCatalogue
->whereIn('t.id', InvestmentPortfolioController::visible()->whereNotNull($foreign)->select($foreign))
->get(['l.opportunity_id', 't.name'])->groupBy('opportunity_id');
}
$features = $missing = [];
$features = $missing = $thematic = [];
$areas = (clone $spatial)->whereIn('shape.geometry_type', ['polygon', 'multipolygon'])->orderBy('shape.id')
->get(['o.id', 'o.title', 'o.slug', 'g.name', 'shape.source_system', 'shape.properties', DB::raw('ST_AsGeoJSON(shape.geom) as geometry'),
DB::raw("CASE WHEN o.primary_territory_id=g.id OR EXISTS (SELECT 1 FROM opportunity_locations direct WHERE direct.opportunity_id=o.id AND direct.geo_location_id=g.id) THEN 'território declarado da oportunidade' ELSE 'contexto do projeto associado; implantação da oportunidade por confirmar' END AS basis")])
->map(fn ($area) => ['geometry' => json_decode($area->geometry, true), 'title' => $area->title.' — '.$area->name.' ('.$area->basis.')',
->map(fn ($area) => ['opportunity_id' => (int) $area->id, 'geometry' => json_decode($area->geometry, true), 'title' => $area->title.' — '.$area->name.' ('.$area->basis.')',
'source_reference' => $area->source_system, 'source_date' => json_decode($area->properties ?? '{}', true)['source_date'] ?? null,
'url' => '/oportunidades/'.$area->slug.($filters === [] ? '' : '?'.http_build_query($filters))])->all();
$located = 0;
@@ -521,6 +536,15 @@ class InvestmentCatalogue
foreach ($relations as $relation => $groups) {
$properties[$relation] = ($groups->get($record->id) ?? collect())->pluck('name')->unique()->values()->all();
}
$public = app(ProactiveInvestment::class)->publicData($record);
$evidence = array_values(array_filter(StructuredData::collectionItems($public['evidence'] ?? []),
fn ($proof) => in_array($proof['category'] ?? null, ['resource', 'infrastructure', 'workforce', 'activity', 'supply', 'demand'], true) && ($proof['purpose'] ?? null) === 'local'));
if ($evidence !== []) {
$thematic[] = ['id' => $record->id, 'title' => $record->title, 'url' => $properties['url'], 'location' => $properties['location'],
'state' => $public['state'] ?? 'hypothesis', 'limitations' => $public['limitations'] ?? null,
'evidence' => array_map(fn ($proof) => [...array_intersect_key($proof, array_flip(['id', 'statement', 'source', 'locator', 'period', 'scope', 'coverage', 'verified_on'])),
'source_category' => $proof['category'], 'category' => in_array($proof['category'], ['activity', 'supply', 'demand'], true) ? 'economic_potential' : $proof['category']], $evidence)];
}
$sites = collect($points->get($record->id) ?? []);
if ($sites->isNotEmpty()) {
$located++;
@@ -537,6 +561,6 @@ class InvestmentCatalogue
return ['type' => 'FeatureCollection', 'limit' => 1000, 'matched_count' => $total, 'truncated' => $total > $records->count(),
'georeferenced_count' => $located, 'marker_count' => count($features), 'unlocated' => $missing, 'features' => $features,
'areas' => $areas, 'complete' => $total === $located];
'thematic_records' => $thematic, 'areas' => $areas, 'complete' => $total === $located];
}
}
+47
View File
@@ -3,6 +3,7 @@
namespace App\Services;
use App\Models\InvestmentDossier;
use App\Models\Opportunity;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\DB;
@@ -12,6 +13,52 @@ final class InvestmentGeography
{
public const FIELDS = ['province_id' => 'province', 'district_id' => 'district', 'admin_post_id' => 'admin_post', 'locality_id' => 'locality'];
/** Public declared coverage; a territory join never multiplies the opportunity total.
*
* @return array{opportunities: int, provinces: int}
*/
public function opportunityStatistics(): array
{
$opportunities = Opportunity::published()->select(['id', 'primary_territory_id', 'province', 'geography_scope'])->toBase();
$references = $this->publicReferences()->select(['id', 'parent_id', 'name', 'location_type']);
$result = DB::selectOne('WITH RECURSIVE
published_opportunities AS ('.$opportunities->toSql().'),
public_references AS ('.$references->toSql().'),
declared_locations AS (
SELECT id AS opportunity_id, primary_territory_id AS location_id
FROM published_opportunities WHERE primary_territory_id IS NOT NULL
UNION
SELECT l.opportunity_id, l.geo_location_id FROM opportunity_locations l
JOIN published_opportunities o ON o.id=l.opportunity_id
),
territory_paths AS (
SELECT d.opportunity_id, d.location_id, g.id, g.parent_id, g.name, g.location_type,
ARRAY[g.id] AS visited, 1 AS depth
FROM declared_locations d JOIN public_references g ON g.id=d.location_id
UNION ALL
SELECT t.opportunity_id, t.location_id, g.id, g.parent_id, g.name, g.location_type,
t.visited||g.id, t.depth+1
FROM territory_paths t JOIN public_references g ON g.id=t.parent_id
WHERE t.depth<5 AND NOT g.id=ANY(t.visited)
),
covered_provinces AS (
SELECT t.name FROM territory_paths t WHERE t.location_type=\'province\'
AND EXISTS (SELECT 1 FROM territory_paths root
WHERE root.opportunity_id=t.opportunity_id AND root.location_id=t.location_id AND root.parent_id IS NULL)
UNION
SELECT g.name FROM public_references g JOIN published_opportunities o
ON lower(btrim(o.province))=lower(btrim(g.name))
WHERE g.location_type=\'province\' AND o.primary_territory_id IS NULL
AND COALESCE(o.geography_scope, \'unspecified\')<>\'national\'
AND NOT EXISTS (SELECT 1 FROM opportunity_locations l WHERE l.opportunity_id=o.id)
)
SELECT (SELECT count(DISTINCT id) FROM published_opportunities) AS opportunities,
(SELECT count(DISTINCT lower(btrim(name))) FROM covered_provinces) AS provinces',
[...$opportunities->getBindings(), ...$references->getBindings()]);
return ['opportunities' => (int) $result->opportunities, 'provinces' => (int) $result->provinces];
}
/** Resolve an old province-name search only when the public register has exactly one match.
* @param array<array-key, mixed> $filters
* @return array<array-key, mixed>
+67
View File
@@ -0,0 +1,67 @@
<?php
namespace App\Services;
use App\Models\InvestmentZone;
use App\Support\StructuredData;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
/** Public park observations remain individual source records, never regional estimates. */
final class InvestmentMapIndicators
{
public const INDICATORS = ['employment', 'investment', 'production', 'exports', 'local_procurement'];
/** @param Collection<int, InvestmentZone> $zones
* @return array<string,mixed> */
public function forZones(Collection $zones, bool $zoneLimitReached = false): array
{
$zoneLimitReached = $zoneLimitReached || $zones->count() > 30;
$zones = $zones->take(30)->values();
$details = app(InvestmentZoneOperationalDetails::class);
$result = ['state' => $details->installed() ? 'available' : 'unavailable', 'records' => [],
'zones_consulted' => $zones->count(), 'zone_limit' => 30, 'record_limit' => 200, 'truncated' => $zoneLimitReached];
if ($result['state'] === 'unavailable') {
return $result;
}
foreach ($zones as $zone) {
// This is the already selected public envelope; never decrypt the private preparation.
$public = $details->publicData($zone);
$observations = StructuredData::collectionItems($public['integral']['observations'] ?? []);
$values = [];
if ($observations !== []) {
// Extract decimal text from this exact loaded snapshot, not a newer database version or a PHP float.
foreach (DB::select("SELECT item->>'id' AS id, item->>'value' AS value FROM jsonb_array_elements((?::jsonb)->'public'->'integral'->'observations') AS item", [$zone->getRawOriginal('operational_details')]) as $number) {
$values[$number->id] = $number->value;
}
}
foreach ($observations as $row) {
if (! in_array($row['indicator'] ?? null, self::INDICATORS, true)) {
continue;
}
if (count($result['records']) === 200) {
$result['truncated'] = true;
return $result;
}
$knowledge = $row['knowledge'] ?? 'unknown';
$rawValue = $values[$row['id'] ?? ''] ?? null;
$value = $knowledge !== 'unknown' && is_numeric($rawValue) ? $rawValue : null;
$missing = [];
foreach (['unit', 'period_start', 'period_end', 'scope', 'population', 'methodology', 'source_reference', 'source_date'] as $field) {
if (trim((string) ($row[$field] ?? '')) === '') {
$missing[] = $field;
}
}
$result['records'][] = [
...array_intersect_key($row, array_flip(['id', 'label', 'description', 'indicator', 'unit', 'period_start', 'period_end', 'scope', 'population', 'methodology', 'source_reference', 'source_date', 'phase_id', 'plot_id'])),
'zone_id' => $zone->id, 'zone_name' => $zone->name, 'zone_version' => (int) $zone->version_no, 'url' => '/zonas/'.$zone->id,
'theme' => $row['indicator'] === 'employment' ? 'workforce' : 'economic_potential',
'value' => $value, 'knowledge' => $knowledge, 'missing_metadata' => $missing,
];
}
}
return $result;
}
}
@@ -0,0 +1,493 @@
<?php
namespace App\Services;
use App\Http\Controllers\InvestmentPortfolioController;
use App\Models\EconomicCluster;
use App\Models\User;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpKernel\Exception\HttpException;
/** Continuation of the existing private dossier, its documentary assessment and proactive evidence. */
final class InvestmentPreparationWorkspace
{
private const SOURCE = 'investor-preparation';
public function __construct(private readonly VirtualDesk $desk) {}
/** @return list<array<array-key,mixed>> */
private function rows(mixed $value): array
{
if (! is_array($value)) {
throw new \LogicException('Dossier list is not recoverable; original retained.');
}
$rows = [];
foreach ($value as $row) {
if (! is_array($row)) {
throw new \LogicException('Dossier row is not recoverable; original retained.');
} $rows[] = $row;
}
return $rows;
}
/** @param array<array-key,mixed> $data */
private function hash(array $data): string
{
return hash('sha256', json_encode(Arr::sortRecursive($data), JSON_THROW_ON_ERROR));
}
private function events(string $id, ?string $opportunity): Builder
{
return DB::table('audit_events')->where('source_system', self::SOURCE)->where('entity_type', 'desk_cases')->where('entity_id', $id)
->where('after_data->scope', $opportunity ?? 'dossier');
}
/** @return array<array-key,mixed> */
private function decode(?\stdClass $event): array
{
return $event ? json_decode(Crypt::decryptString(json_decode($event->after_data, true, 512, JSON_THROW_ON_ERROR)['snapshot']), true, 512, JSON_THROW_ON_ERROR)
: ['version' => 0, 'apiex' => ['additional_documents' => [], 'note' => '', 'review' => null, 'exports' => [], 'external' => []],
'swot' => ['object_key' => '', 'scope' => '', 'rows' => [], 'source_fingerprint' => '', 'review' => null, 'actions' => []]];
}
/** @return array<array-key,mixed> */
private function current(string $id, ?string $opportunity): array
{
return $this->decode($this->events($id, $opportunity)->orderByRaw("(after_data->>'version')::integer DESC")->first());
}
/** @return array{0: \stdClass, 1: array<array-key,mixed>, 2: ?string, 3: array<array-key,mixed>, 4: bool} */
private function context(User $actor, string $id, ?string $opportunity, bool $initialScope = false): array
{
abort_unless(app(InvestorPath::class)->available() && app(PrivateInvestmentPreparation::class)->available(), 404);
$case = $this->desk->case($actor, $id);
abort_unless($this->desk->owns($actor, $case, false) || $actor->can('manage-portal'), 403);
$context = app(InvestorContext::class)->state($case);
$scope = app(InvestmentConsumerScope::class);
$historical = false;
abort_if($initialScope && ! $this->events($id, null)->exists(), 404);
$choices = array_column(array_column($context['opportunity_choices'] ?? [], 'opportunity'), 'key');
if ((($opportunity && ! in_array($opportunity, $choices, true)) || ($initialScope && $choices)) && $this->events($id, $opportunity)->exists()) {
$saved = $this->current($id, $opportunity);
$last = $saved['apiex']['exports'] ? $saved['apiex']['exports'][array_key_last($saved['apiex']['exports'])] : null;
$oldContext = $saved['context_snapshot'] ?? $last['prepared']['context'] ?? $saved['swot']['sources_snapshot']['context'] ?? null;
abort_unless(is_array($oldContext), 409, 'Contexto histórico conservado; snapshot de consulta indisponível.');
$context = $oldContext;
$historical = true;
} else {
$opportunity = $scope->key($context, $opportunity, true);
$context = $scope->context($context, $opportunity);
}
$documents = app(InvestorContext::class)->preparationDocuments($id);
// Reuse request-level document authorization, including for a dossier team member.
$documents = array_values(array_filter($documents, function ($document) use ($actor, $id) {
$link = DB::table('desk_documents')->where('id', $document['id'])->where('case_id', $id)->first();
if ($link?->request_id) {
try {
$this->desk->request($actor, $link->request_id);
} catch (HttpException $e) {
if (in_array($e->getStatusCode(), [403, 404], true)) {
return false;
} throw $e;
}
}
return true;
}));
foreach ($documents as &$document) {
$record = DB::table('documents')->where('id', $document['document_id'])->first();
$document['available'] = $record && $record->status === 'active' && $record->malware_scan_status === 'clean'
&& hash_equals(trim($record->sha256), trim((string) $record->malware_scanned_sha256));
}
unset($document);
$unique = [];
foreach ($documents as $document) {
$unique[$document['document_id']] = $document;
}
return [$case, $context, $opportunity, array_values($unique), $historical];
}
/** @param array<array-key,mixed> $context
* @return array<array-key,mixed> */
private function sources(User $actor, \stdClass $case, array $context, string $object): array
{
$objects = [['key' => 'investment:'.$case->id, 'label' => $case->title, 'kind' => 'investment']];
$evidence = [];
foreach ($context['sector_references'] ?? [] as $reference) {
$current = hash_equals((SectorResourceReferences::SOURCES[$reference['sector_slug']] ?? ''), $reference['source_sha256'] ?? '') ? $reference : ['unavailable' => true, 'source_sha256' => (SectorResourceReferences::SOURCES[$reference['sector_slug']] ?? '')];
$evidence['sector-resource:'.$reference['key']] = ['key' => 'sector-resource:'.$reference['key'], 'label' => $reference['title'], 'consulted' => $reference, 'current' => $current, 'notice' => 'Referência sectorial e de cadeia de valor; não certifica direitos, licenças nem disponibilidade de projecto.'];
}
$refs = array_values($context['selection'] ?? []);
foreach ($context['opportunity_choices'] ?? [] as $choice) {
$refs[] = $choice['opportunity'];
}
foreach ($refs as $ref) {
if (! is_array($ref) || empty($ref['key'])) {
continue;
}
try {
$live = app(InvestorContext::class)->reference($ref['key']);
} catch (ModelNotFoundException|HttpException $e) {
$live = ['key' => $ref['key'], 'unavailable' => true];
}
$evidence[$ref['key']] = ['key' => $ref['key'], 'label' => $ref['title'] ?? $ref['key'], 'consulted' => $ref, 'current' => $live];
if (in_array($ref['kind'] ?? '', ['territory', 'sector'], true)) {
$objects[] = ['key' => $ref['key'], 'label' => $ref['title'], 'kind' => $ref['kind']];
}
if (($ref['kind'] ?? '') === 'portfolio') {
$portfolio = InvestmentPortfolioController::visible()->whereKey($ref['id'])->first();
if ($portfolio?->cluster_id) {
$cluster = EconomicCluster::find($portfolio->cluster_id);
if ($cluster) {
$objects[] = ['key' => 'cluster:'.$cluster->id, 'label' => $cluster->getAttribute('name'), 'kind' => 'cluster'];
}
}
}
}
// Sector/territory may have been deliberately entered rather than selected as a source.
foreach ([['sector', 'sector_id'], ['territory', 'province_id'], ['territory', 'district_id'], ['territory', 'admin_post_id'], ['territory', 'locality_id']] as [$kind, $field]) {
if (! empty($context['fields'][$field])) {
$key = $kind.':'.$context['fields'][$field];
try {
$ref = app(InvestorContext::class)->reference($key);
$objects[] = ['key' => $key, 'label' => $ref['title'], 'kind' => $kind];
$evidence[$key] = ['key' => $key, 'label' => $ref['title'], 'consulted' => $ref, 'current' => $ref];
} catch (ModelNotFoundException|HttpException $e) { /* Keep the declared context; do not invent evidence. */
}
}
}
// A manually chosen cluster must already be a public, visible portfolio association.
if (str_starts_with($object, 'cluster:')) {
$clusterId = substr($object, 8);
$visible = InvestmentPortfolioController::visible()->where('cluster_id', $clusterId)->exists();
$cluster = $visible ? EconomicCluster::find($clusterId) : null;
$objects[] = ['key' => $object, 'label' => $cluster ? $cluster->getAttribute('name') : 'Cluster da análise guardada — fonte actual indisponível', 'kind' => 'cluster'];
}
foreach ($objects as $item) {
if ($item['kind'] === 'cluster') {
$evidence[$item['key']] = ['key' => $item['key'], 'label' => 'Competitividade — '.$item['label'],
'current' => InvestmentPortfolioController::visible()->where('cluster_id', substr($item['key'], 8))->exists() ? app(ClusterCompetitiveness::class)->publicData(substr($item['key'], 8)) : null, 'notice' => 'Versão pública aprovada; ausência de análise não prova ausência de oferta/concorrência.'];
}
}
$unique = [];
foreach ($objects as $item) {
$unique[$item['key']] = $item;
}
return [array_values($unique), array_values($evidence)];
}
/** @return array<array-key,mixed> */
public function view(User $actor, string $id, ?string $opportunity = null, ?int $historyBefore = null, bool $initialScope = false): array
{
[$case, $context, $opportunity, $documents, $historical] = $this->context($actor, $id, $opportunity, $initialScope);
$state = $this->current($id, $opportunity);
[$objects, $sources] = $this->sources($actor, $case, $context, $state['swot']['object_key']);
$payload = $this->desk->decode($case->payload);
$reused = ['investor' => Arr::only($payload, ['name', 'email']), 'profile' => Arr::only($payload['profile'] ?? [], ['phone', 'nuit', 'address', 'country_code', 'nationality', 'residence_country_code']), 'company' => Arr::only($payload['company'] ?? [], ['id', 'name', 'nuit', 'registration', 'address', 'type']), 'company_version' => $payload['company_version'] ?? null, 'notice' => 'Valores autorizados conservados no dossier; não foram substituídos por dados recentes de outra origem.'];
if ($historical && isset($state['reused_snapshot'])) {
$reused = $state['reused_snapshot'];
}
foreach ($documents as $document) {
$sources[] = ['key' => 'document:'.$document['document_id'], 'label' => $document['file_name'], 'current' => $document, 'notice' => 'Documento privado do dossier; disponibilidade não certifica o conteúdo.'];
}
$assessment = app(PrivateInvestmentPreparation::class)->assess($context, $documents);
$issues = $assessment['missing_labels'];
if (! $assessment['enabled'] || ! $assessment['period_covered'] || ! $assessment['scope_covered'] || $assessment['mode'] === 'indeterminate') {
$issues[] = 'Completar o enquadramento da preparação existente.';
}
foreach ($assessment['requirements'] as &$requirement) {
$document = collect($this->rows($documents))->firstWhere('document_id', $requirement['document_id']);
$requirement['available'] = $document['available'] ?? false;
if ($requirement['state'] === 'indeterminate' || ($requirement['required'] && ! $requirement['available'])) {
$issues[] = $requirement['label'];
}
}
unset($requirement);
$selected = array_values(array_unique([...collect($this->rows($assessment['requirements']))->where('required', true)->pluck('document_id')->filter()->all(), ...$state['apiex']['additional_documents']]));
foreach ($selected as $doc) {
if (! (collect($this->rows($documents))->firstWhere('document_id', $doc)['available'] ?? false)) {
$issues[] = 'Anexo indisponível: '.$doc;
}
}
$documentary = array_values(array_filter($documents, fn ($d) => in_array($d['document_id'], $selected, true)));
$fingerprint = $this->hash(['context' => $context, 'reused' => $reused, 'assessment' => $assessment, 'documents' => $documentary, 'note' => $state['apiex']['note']]);
$swotFingerprint = $this->hash($this->sourceSnapshot($context, $reused, $sources, $state['swot']['object_key'], $state['swot']['rows']));
$external = collect($this->rows($state['apiex']['external']));
$apiState = $external->contains('kind', 'decision') ? 'decision_received' : ($external->contains('kind', 'delivery') ? 'delivery_recorded' : (! $issues && ($state['apiex']['review']['fingerprint'] ?? null) === $fingerprint ? 'ready' : 'preparing'));
$query = $opportunity ? '?opportunity='.urlencode($opportunity) : '';
$editQuery = $historical ? '' : $query;
$savedScopes = DB::table('audit_events')->where('source_system', self::SOURCE)->where('entity_type', 'desk_cases')->where('entity_id', $id)->selectRaw("DISTINCT after_data->>'scope' as scope")->get()->map(fn ($row) => ['key' => $row->scope, 'label' => $row->scope === 'dossier' ? 'Contexto inicial do dossier' : $row->scope, 'href' => '/investimentos/'.$id.'/preparacao?'.($row->scope === 'dossier' ? 'history_scope=dossier' : 'opportunity='.urlencode($row->scope))])->all();
$historyRows = $this->events($id, $opportunity)->when($historyBefore !== null, fn ($q) => $q->whereRaw("(after_data->>'version')::integer < ?", [$historyBefore]))->orderByRaw("(after_data->>'version')::integer DESC")->limit(26)->get();
$history = $historyRows->take(25)->map(fn ($e) => ['version' => json_decode($e->after_data, true)['version'], 'action' => $e->action, 'at' => $e->occurred_at, 'actor' => $e->actor_user_id, 'operation_key' => $e->correlation_id])->values()->all();
return ['case' => ['id' => $id, 'title' => $case->title, 'closed' => $case->closed_at !== null], 'opportunity' => $opportunity, 'opportunity_label' => $context['selection']['opportunity']['title'] ?? null,
'historical' => $historical, 'initial_scope' => $initialScope, 'saved_scopes' => $savedScopes, 'version' => $state['version'], 'apiex' => $state['apiex'], 'swot' => $state['swot'], 'assessment' => $assessment, 'context' => $context, 'reused' => $reused,
'documents' => $documents, 'selected_documents' => $documentary, 'api_issues' => array_values(array_unique($issues)), 'api_state' => $apiState,
'api_fingerprint' => $fingerprint, 'swot_fingerprint' => $swotFingerprint,
'swot_review_required' => $state['swot']['source_fingerprint'] !== $swotFingerprint || ($state['swot']['review']['fingerprint'] ?? null) !== $swotFingerprint,
'objects' => $objects, 'sources' => $sources, 'can_write' => ! $historical && ! $case->closed_at && $this->desk->owns($actor, $case),
'can_review' => ! $historical && ! $case->closed_at && ($this->desk->owns($actor, $case) || $actor->can('manage-portal')),
'links' => ['dossier' => '/investimentos/'.$id.$editQuery, 'context' => '/investimentos/'.$id.'/contexto/editar'.$editQuery,
'documents' => '/balcao-virtual/dossiers/'.$id.$query, 'partners' => '/parcerias/painel?context='.$id.($opportunity ? '&opportunity='.urlencode($opportunity) : ''),
'library' => '/documentos?context='.$id.($opportunity ? '&opportunity='.urlencode($opportunity) : ''),
'editorial' => '/acesso-gestao', 'idea' => '/investimentos/'.$id.'/contexto/editar'.$query.'#project-summary', 'opportunity' => '/investimentos/'.$id.'/contexto/editar'.$query.'#project-summary', 'risk' => '/investimentos/'.$id.$query.'#tarefas'],
'history' => $history, 'history_next' => $historyRows->count() > 25 ? ($history[count($history) - 1]['version'] ?? null) : null];
}
/** @param array<array-key,mixed> $context
* @param array<array-key,mixed> $reused
* @param array<array-key,mixed> $sources
* @param array<array-key,mixed> $rows
* @return array<array-key,mixed> */
private function sourceSnapshot(array $context, array $reused, array $sources, string $object, array $rows): array
{
$used = array_column($rows, 'source_key');
$tracked = array_values(array_filter($sources, fn ($source) => ! str_starts_with($source['key'], 'document:') || in_array($source['key'], $used, true)));
return ['context' => $context, 'reused' => $reused, 'sources' => $tracked, 'object' => $object];
}
/** @return array<array-key,mixed>|null */
public function continuation(User $actor, string $id, ?string $opportunity, mixed $actionId): ?array
{
if (! is_string($actionId) || ! Str::isUuid($actionId)) {
return null;
}
[, , $scope] = $this->context($actor, $id, $opportunity);
return collect($this->rows($this->current($id, $scope)['swot']['actions']))->firstWhere('id', $actionId);
}
/** @return list<string> */
public function taskKeys(User $actor, string $id, string $opportunity): array
{
[, , $scope] = $this->context($actor, $id, $opportunity);
return array_values(array_map(fn ($action) => 'swot:'.$action['id'], $this->current($id, $scope)['swot']['actions']));
}
/** @param array<array-key,mixed> $input
* @param array<array-key,mixed> $view
* @return array<array-key,mixed> */
private function draft(array $input, array $view): array
{
$data = Validator::make($input, ['object_key' => ['required', Rule::in(array_column($view['objects'], 'key'))], 'scope' => 'present|nullable|string|max:4000',
'source_fingerprint' => 'present|string|size:64', 'rows' => 'present|array|list|max:80', 'rows.*' => 'array:id,dimension,text,reference,source_key,date,classification',
'rows.*.id' => 'required|uuid|distinct', 'rows.*.dimension' => 'required|in:strength,weakness,opportunity,threat',
'rows.*.classification' => 'required|in:documented_fact,hypothesis,unconfirmed', 'rows.*.text' => 'present|nullable|string|max:4000',
'rows.*.reference' => 'present|nullable|string|max:4000', 'rows.*.source_key' => ['nullable', Rule::in(array_column($view['sources'], 'key'))], 'rows.*.date' => 'nullable|date_format:Y-m-d'])->validate();
$snapshot = $this->sourceSnapshot($view['context'], $view['reused'], $view['sources'], $data['object_key'], $data['rows']);
return [...$data, 'sources_snapshot' => $snapshot, 'snapshot_fingerprint' => $this->hash($snapshot), 'review' => null, 'actions' => $view['swot']['actions']];
}
/** @param array<array-key,mixed> $input
* @return array<array-key,mixed> */
public function change(User $actor, string $id, array $input): array
{
$control = Validator::make($input, ['opportunity' => 'nullable|string|max:150', 'version' => 'required|integer|min:0', 'operation_key' => 'required|uuid',
'action' => 'required|in:apiex_save,apiex_review,apiex_export,external,swot_save,swot_review,swot_action,swot_restore', 'reason' => 'required|string|min:3|max:4000'])->validate();
return DB::transaction(function () use ($actor, $id, $input, $control) {
$case = $this->desk->case($actor, $id, false, true);
$this->desk->assertOpen($case);
$view = $this->view($actor, $id, $control['opportunity'] ?? null);
$review = in_array($control['action'], ['apiex_review', 'swot_review'], true);
abort_unless($review ? $view['can_review'] : $view['can_write'], 403);
$requestHash = $this->hash([...$input, 'actor' => $actor->id]);
$prior = $this->events($id, $view['opportunity'])->where('correlation_id', $control['operation_key'])->first();
if ($prior) {
abort_unless(hash_equals(json_decode($prior->after_data, true)['request_hash'], $requestHash), 409, 'A tentativa já tem outro conteúdo.');
return [...$this->view($actor, $id, $view['opportunity']), 'replayed' => true];
}
abort_unless((int) $control['version'] === $view['version'], 409, 'Existe outra versão. Os seus campos foram preservados; consulte e compare antes de substituir.');
$state = Arr::only($view, ['version', 'apiex', 'swot']);
$receipt = ['actor' => $actor->id, 'at' => now()->toISOString(), 'reason' => $control['reason']];
switch ($control['action']) {
case 'apiex_save':
$data = Validator::make($input, ['note' => 'present|nullable|string|max:6000', 'additional_documents' => 'present|array|list|max:60', 'additional_documents.*' => ['required', 'distinct', Rule::in(array_column($view['documents'], 'document_id'))]])->validate();
$state['apiex'] = [...$state['apiex'], ...$data, 'review' => null];
break;
case 'apiex_review':
abort_if($view['api_issues'], 422, 'Complete os requisitos e documentos aplicáveis.');
abort_unless(($input['fingerprint'] ?? '') === $view['api_fingerprint'], 409, 'O dossier/documentos mudaram; reveja a versão actual.');
foreach ($view['selected_documents'] as $document) {
$record = DB::table('documents')->where('id', $document['document_id'])->whereNull('deleted_at')->lockForUpdate()->first();
abort_unless($record !== null, 404);
app(VerifiedDocumentBinary::class)->respond($record);
}
$state['apiex']['review'] = [...$receipt, 'fingerprint' => $view['api_fingerprint']];
break;
case 'apiex_export':
abort_if($view['api_issues'] || ($state['apiex']['review']['fingerprint'] ?? '') !== $view['api_fingerprint'], 409, 'Reveja a preparação actual antes de exportar.');
$prepared = Arr::only($view, ['case', 'opportunity', 'context', 'reused', 'assessment', 'selected_documents']);
$prepared['apiex'] = Arr::only($state['apiex'], ['note', 'review']);
$state['apiex']['exports'][] = [...$receipt, 'id' => $control['operation_key'], 'fingerprint' => $view['api_fingerprint'], 'context_revision' => $view['context']['revision'], 'documents' => $view['selected_documents'], 'prepared' => $prepared];
break;
case 'external':
$data = Validator::make($input, ['kind' => 'required|in:delivery,clarification,response,decision', 'date' => 'required|date_format:Y-m-d|before_or_equal:today', 'reference' => 'required|string|max:2000',
'proof_document' => ['required', Rule::in(collect($this->rows($view['documents']))->where('available', true)->pluck('document_id')->all())], 'export_id' => 'required|uuid', 'related_id' => 'nullable|uuid', 'outcome' => 'nullable|in:approved,refused,conditional,other'])->validate();
abort_unless(collect($this->rows($state['apiex']['exports']))->contains('id', $data['export_id']), 422, 'Escolha a versão exportada deste dossier/oportunidade.');
$same = collect($this->rows($state['apiex']['external']))->where('export_id', $data['export_id']);
if ($data['kind'] !== 'delivery') {
abort_unless($same->contains('kind', 'delivery'), 422, 'Registe a entrega externa desta versão.');
}
if ($data['kind'] === 'response') {
abort_unless($same->where('kind', 'clarification')->contains('id', $data['related_id'] ?? ''), 422, 'Identifique o pedido de esclarecimento a que responde.');
}
if ($data['kind'] === 'decision') {
abort_unless(! empty($data['outcome']), 422, 'Identifique a decisão efectivamente recebida.');
}
$record = DB::table('documents')->where('id', $data['proof_document'])->whereNull('deleted_at')->lockForUpdate()->first();
abort_unless($record !== null, 404);
app(VerifiedDocumentBinary::class)->respond($record);
$state['apiex']['external'][] = [...$data, ...$receipt, 'id' => $control['operation_key'], 'proof_snapshot' => collect($this->rows($view['documents']))->firstWhere('document_id', $data['proof_document'])];
break;
case 'swot_save':
$state['swot'] = $this->draft($input['draft'] ?? [], $view);
break;
case 'swot_review':
$swot = $state['swot'];
abort_unless(($input['fingerprint'] ?? '') === $view['swot_fingerprint'], 409, 'As fontes mudaram; compare antes de rever.');
abort_unless(trim($swot['scope'] ?? '') !== '' && in_array($swot['object_key'], array_column($view['objects'], 'key'), true), 422, 'Identifique objecto e âmbito.');
foreach (['strength', 'weakness', 'opportunity', 'threat'] as $dimension) {
abort_unless(collect($this->rows($swot['rows']))->contains('dimension', $dimension), 422, 'Trate as quatro dimensões; incerteza deve ficar classificada.');
}
foreach ($swot['rows'] as $row) {
abort_unless(trim($row['text'] ?? '') !== '' && trim($row['reference'] ?? '') !== '' && ! empty($row['date']), 422, 'Cada conclusão exige fonte, data e classificação; desconhecido não é ausência.');
}
$state['swot']['source_fingerprint'] = $view['swot_fingerprint'];
$state['swot']['sources_snapshot'] = $this->sourceSnapshot($view['context'], $view['reused'], $view['sources'], $state['swot']['object_key'], $state['swot']['rows']);
$state['swot']['snapshot_fingerprint'] = $view['swot_fingerprint'];
$state['swot']['review'] = [...$receipt, 'fingerprint' => $view['swot_fingerprint'], 'version' => $state['version'] + 1];
break;
case 'swot_action':
abort_if($view['swot_review_required'], 409, 'Reveja a análise e as fontes antes de ligar uma acção.');
$data = Validator::make($input, ['conclusion_id' => ['required', Rule::in(array_column($state['swot']['rows'], 'id'))], 'kind' => 'required|in:idea,opportunity,partners,risk', 'title' => 'required|string|min:3|max:200'])->validate();
$href = $view['links'][$data['kind']];
if ($data['kind'] === 'risk') {
$href = str_replace('#tarefas', (str_contains($href, '?') ? '&' : '?').'swot_action='.$control['operation_key'].'#tarefas', $href);
}
$state['swot']['actions'][] = [...$data, ...$receipt, 'id' => $control['operation_key'], 'case_id' => $id, 'opportunity' => $view['opportunity'], 'review_version' => $state['swot']['review']['version'], 'href' => $href];
break;
case 'swot_restore':
$data = Validator::make($input, ['revision' => 'required|integer|min:1'])->validate();
$event = $this->events($id, $view['opportunity'])->where('after_data->version', $data['revision'])->first();
abort_unless($event !== null, 404);
$old = $this->decode($event)['swot'];
$state['swot'] = [...$old, 'review' => null, 'actions' => $state['swot']['actions']];
break;
}
$state['context_snapshot'] = $view['context'];
$state['reused_snapshot'] = $view['reused'];
$state['version']++;
DB::table('audit_events')->insert(['source_system' => self::SOURCE, 'entity_type' => 'desk_cases', 'entity_id' => $id, 'external_id' => $id.':'.hash('sha256', $view['opportunity'] ?? 'dossier').':'.$state['version'],
'correlation_id' => $control['operation_key'], 'actor_user_id' => $actor->id, 'action' => $control['action'], 'data_classification' => 'restricted',
'before_data' => json_encode(['version' => $view['version']], JSON_THROW_ON_ERROR), 'after_data' => json_encode(['version' => $state['version'], 'scope' => $view['opportunity'] ?? 'dossier', 'request_hash' => $requestHash, 'snapshot' => Crypt::encryptString(json_encode($state, JSON_THROW_ON_ERROR))], JSON_THROW_ON_ERROR)]);
return [...$this->view($actor, $id, $view['opportunity']), 'replayed' => false];
}, 3);
}
/** Export uses the existing verified binary reader, never a browser-supplied path.
* @return array{bytes:string,export_id:string} */
public function bundle(User $actor, string $id, ?string $opportunity, string $exportId, bool $initialScope = false): array
{
return DB::transaction(function () use ($actor, $id, $opportunity, $exportId, $initialScope) {
$this->desk->case($actor, $id, false, true);
if ($opportunity === null && ! $initialScope) {
$scopes = DB::table('audit_events')->where('source_system', self::SOURCE)->where('entity_type', 'desk_cases')->where('entity_id', $id)->where('correlation_id', $exportId)->where('action', 'apiex_export')->selectRaw("DISTINCT after_data->>'scope' as scope")->get();
abort_unless($scopes->count() === 1, 422, 'Identifique o âmbito da exportação preservada.');
$scope = $scopes->first()->scope;
$initialScope = $scope === 'dossier';
$opportunity = $initialScope ? null : $scope;
}
$view = $this->view($actor, $id, $opportunity, null, $initialScope);
$export = collect($this->rows($view['apiex']['exports']))->firstWhere('id', $exportId);
abort_unless($export !== null, 404);
$records = [];
foreach ($export['documents'] as $document) {
abort_unless(collect($this->rows($view['documents']))->contains('document_id', $document['document_id']), 403);
$record = DB::table('documents')->where('id', $document['document_id'])->whereNull('deleted_at')->lockForUpdate()->first();
abort_unless($record !== null && $record->status === 'active' && $record->malware_scan_status === 'clean' && hash_equals(trim($document['sha256']), trim($record->sha256)) && hash_equals(trim($record->sha256), trim((string) $record->malware_scanned_sha256)), 404, 'Documento da versão preparada indisponível para descarga; o histórico foi preservado.');
app(VerifiedDocumentBinary::class)->authorizeOpportunityAttachment($record);
$records[$document['document_id']] = $record;
}
$disk = Storage::disk('local');
$key = 'investor-preparations/'.$id.'/'.hash('sha256', $view['opportunity'] ?? 'dossier').'/'.$exportId.'.zip.enc';
if ($disk->exists($key)) {
try {
$sealed = json_decode(Crypt::decryptString($disk->get($key)), true, 512, JSON_THROW_ON_ERROR);
} catch (DecryptException|\JsonException $e) {
abort(404, 'Pacote preservado indisponível; não foi substituído.');
}
abort_unless(($sealed['case_id'] ?? '') === $id && ($sealed['opportunity'] ?? null) === $view['opportunity'] && ($sealed['export_id'] ?? '') === $exportId && ($sealed['fingerprint'] ?? '') === $export['fingerprint'], 404);
$bytes = base64_decode($sealed['binary'] ?? '', true);
abort_unless(is_string($bytes) && hash_equals($sealed['sha256'] ?? '', hash('sha256', $bytes)), 404);
return ['bytes' => $bytes, 'export_id' => $exportId];
}
$prepared = $export['prepared'];
abort_unless(is_array($prepared), 409, 'Snapshot de exportação indisponível; original conservado.');
$file = tempnam(sys_get_temp_dir(), 'investmoz-apiex-');
abort_unless(is_string($file), 503);
$zip = new \ZipArchive;
$open = false;
try {
abort_unless($zip->open($file, \ZipArchive::OVERWRITE) === true, 503);
$open = true;
$manifest = ['format' => 'investmoz-apiex-preparation/1', 'case_id' => $id, 'opportunity' => $view['opportunity'], 'export' => $export, 'context' => $prepared['context'], 'reused' => $prepared['reused'], 'assessment' => $prepared['assessment'], 'notice' => 'Preparação para entrega externa. Não é formulário oficial, submissão electrónica ou aprovação.'];
$index = view('investments.preparation-index', ['workspace' => $prepared, 'manifest' => $manifest])->render();
abort_unless($zip->addFromString('indice.html', $index), 503);
abort_unless($zip->addFromString('manifesto.json', json_encode($manifest, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)), 503);
foreach ($export['documents'] as $n => $document) {
$record = $records[$document['document_id']];
$response = app(VerifiedDocumentBinary::class)->respond($record);
$bytes = $response instanceof BinaryFileResponse ? file_get_contents($response->getFile()->getPathname()) : $response->getContent();
abort_unless(is_string($bytes), 503);
abort_unless($zip->addFromString('anexos/'.sprintf('%02d-', $n + 1).preg_replace('/[^\pL\pN._-]+/u', '-', basename($document['file_name'])), $bytes), 503);
}
abort_unless($zip->close(), 503);
$open = false;
$bytes = file_get_contents($file);
abort_unless(is_string($bytes), 503);
$sealed = ['case_id' => $id, 'opportunity' => $view['opportunity'], 'export_id' => $exportId, 'fingerprint' => $export['fingerprint'], 'sha256' => hash('sha256', $bytes), 'binary' => base64_encode($bytes)];
$temporary = $key.'.'.Str::uuid().'.tmp';
try {
abort_unless($disk->put($temporary, Crypt::encryptString(json_encode($sealed, JSON_THROW_ON_ERROR))) && $disk->move($temporary, $key), 503, 'Não foi possível preservar o pacote; repita a mesma exportação.');
} finally {
$disk->delete($temporary);
}
return ['bytes' => $bytes, 'export_id' => $exportId];
} finally {
if ($open) {
$zip->close();
}
if (is_file($file)) {
unlink($file);
}
}
});
}
}
+3
View File
@@ -142,6 +142,9 @@ final class InvestorContext
$zone = $context['selection']['park'] ?? null;
$context['fields']['investment_regime'] = $zone['fields']['investment_regime'] ?? 'general';
$context['fields']['special_zone_id'] = $zone['id'] ?? '';
if (! empty($payload['investor_path']['sector_references'])) {
$context['sector_references'] = $payload['investor_path']['sector_references'];
}
return $context;
}
+14 -5
View File
@@ -160,7 +160,9 @@ final class InvestorPath
$next = ['code' => 'S06', 'title' => $preparation['next'], 'action' => 'Preparar', 'href' => '/investimentos/'.$id.'/contexto/editar#preparation-questions'];
}
return ['private_investment' => $preparation, 'case' => ['id' => $id, 'title' => $case->title, 'version' => $case->version, 'closed' => $case->closed_at !== null],
$preparationTaskKeys = $opportunity !== null && ($owner || $manager) && app(PrivateInvestmentPreparation::class)->available() ? app(InvestmentPreparationWorkspace::class)->taskKeys($actor, $id, $opportunity) : null;
return ['funding_selections' => ($owner || $manager) ? array_values(array_filter($path['funding_selections'] ?? [], fn ($row) => ($row['opportunity'] ?? null) === $opportunity)) : [], 'preparation_action' => ($owner || $manager) && app(PrivateInvestmentPreparation::class)->available() ? app(InvestmentPreparationWorkspace::class)->continuation($actor, $id, $opportunity, request()->query('swot_action')) : null, 'private_investment' => $preparation, 'case' => ['id' => $id, 'title' => $case->title, 'version' => $case->version, 'closed' => $case->closed_at !== null],
'journey_id' => $journey->id, 'definition_id' => $version->id, 'definition_version' => $version->version_no, 'source_sha256' => $definition['source_sha256'],
'phases' => $definition['phases'], 'steps' => $steps, 'facts' => $facts, 'fact_reasons' => $factReasons,
'context_opportunity' => $opportunity, 'opportunity_options' => $allChoices,
@@ -171,13 +173,16 @@ final class InvestorPath
'results' => $results, 'requests' => $requests, 'documents' => $documents,
'zone_contexts' => ($owner || $manager) ? ($path['zone_contexts'] ?? []) : [],
'sector_contexts' => ($owner || $manager) ? ($path['sector_contexts'] ?? []) : [],
'sector_references' => ($owner || $manager) ? ($path['sector_references'] ?? []) : [],
'content_sources' => ($owner || $manager) ? ($path['content_sources'] ?? []) : [],
'tasks' => DB::table('tasks')->where('desk_case_id', $id)
->whereNull('deleted_at')
->when($preparationTaskKeys !== null, fn ($q) => $q->where(fn ($p) => $p->whereNull('guidance_key')->orWhere('guidance_key', 'not like', 'swot:%')->orWhereIn('guidance_key', $preparationTaskKeys ?? [])))
->when(! $owner && ! $manager, fn ($q) => $q->where('assignee_user_id', $actor->id))
->get(['id', 'title', 'status', 'version_no', 'due_at', 'assignee_user_id']),
'services' => app(DeskServiceAvailability::class)->current()->get()->map(fn ($s) => ['id' => $s->id, 'title' => json_decode($s->definition, true)['title'], 'effective_from' => $s->effective_from ?? null]),
'tools' => ['simulator' => (int) $case->user_id === $actor->id ? '/simulador-fiscal?context='.$id.($case->simulation_id ? '&draft='.$case->simulation_id : '').($opportunity ? '&opportunity='.urlencode($opportunity) : '') : null,
'tools' => ['funding' => ($owner || $manager) && app(FundingProgrammes::class)->installed() ? '/fundos?kind=institution&location_mode=physical&context='.$id.($opportunity ? '&opportunity='.urlencode($opportunity) : '') : null, 'simulator' => (int) $case->user_id === $actor->id ? '/simulador-fiscal?context='.$id.($case->simulation_id ? '&draft='.$case->simulation_id : '').($opportunity ? '&opportunity='.urlencode($opportunity) : '') : null,
'preparation' => ($owner || $manager) && app(PrivateInvestmentPreparation::class)->available() ? '/investimentos/'.$id.'/preparacao'.($opportunity ? '?opportunity='.urlencode($opportunity) : '') : null,
'company' => '/investimentos/'.$id.'/forma-juridica',
'desk' => '/balcao-virtual/dossiers/'.$id.($opportunity ? '?opportunity='.urlencode($opportunity) : ''), 'guidance' => '/balcao-virtual/dossiers/'.$id.'/orientacao',
'library' => '/documentos?context='.$id, 'opportunities' => '/oportunidades?context='.$id, 'territory' => '/zonas?context='.$id, 'partners' => '/parcerias/painel?context='.$id,
@@ -337,12 +342,16 @@ final class InvestorPath
public function task(User $actor, string $id, array $input): string
{
$data = Validator::make($input, ['version' => 'required|integer|min:1', 'code' => ['required', Rule::in(array_map(fn ($n) => sprintf('S%02d', $n), range(1, 19)))],
'preparation_action' => 'nullable|uuid', 'opportunity' => 'nullable|string|max:150',
'title' => 'required|string|min:3|max:200', 'due_at' => 'required|date|after:now', 'consent' => 'accepted'])->validate();
return DB::transaction(function () use ($actor, $id, $data): string {
$case = $this->desk->case($actor, $id, true, true);
abort_if($case->closed_at, 409);
$key = 'path:'.$data['code'];
if (!empty($data['preparation_action'])) { app(InvestmentConsumerScope::class)->key(app(InvestorContext::class)->state($case), $data['opportunity'] ?? null, true); }
$origin = !empty($data['preparation_action']) ? app(InvestmentPreparationWorkspace::class)->continuation($actor, $id, $data['opportunity'] ?? null, $data['preparation_action']) : null;
abort_if(!empty($data['preparation_action']) && (!$origin || $origin['kind'] !== 'risk'), 422, 'A acção SWOT não pertence a este dossier/oportunidade ou não é uma acção de risco.');
$key = $origin ? 'swot:'.$origin['id'] : 'path:'.$data['code'];
$existing = DB::table('tasks')->where('desk_case_id', $id)->where('guidance_key', $key)->first();
if ($existing) {
abort_unless($existing->title === $data['title'] && (int) $existing->accepted_by === $actor->id
@@ -351,12 +360,12 @@ final class InvestorPath
return $existing->id;
}
$this->writable($actor, $id, $data['version']);
$step = $this->findStep($this->view($actor, $id), $data['code']);
$step = $this->findStep($this->view($actor, $id, $origin['opportunity'] ?? null), $data['code']);
abort_unless($step !== null && $step['preparation_allowed'], 422);
$task = CrmTask::create(['desk_case_id' => $id, 'guidance_key' => $key, 'title' => $data['title'], 'due_at' => $data['due_at'],
'status' => 'open', 'assignee_user_id' => $actor->id, 'accepted_by' => $actor->id, 'accepted_at' => now(),
'external_sync_status' => 'pending_authorization', 'data_classification' => 'confidential', 'source_system' => 'investor-path']);
$this->desk->event($case, $actor, 'path_task_accepted', ['task_id' => $task->id, 'code' => $data['code'], 'scope' => 'internal_crm']);
$this->desk->event($case, $actor, 'path_task_accepted', ['task_id' => $task->id, 'code' => $data['code'], 'scope' => 'internal_crm', 'preparation_action' => $origin['id'] ?? null, 'opportunity' => $origin['opportunity'] ?? null, 'swot_review_version' => $origin['review_version'] ?? null]);
return $task->id;
});
+132
View File
@@ -0,0 +1,132 @@
<?php
namespace App\Services;
use App\Models\Opportunity;
use App\Models\User;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
class OpportunityCuration
{
public function __construct(private PortalProfileAccess $access, private OpportunityManagement $management) {}
public function allowed(User $actor, Opportunity $record): bool
{
if (! $this->management->installed() || ! $this->access->canView($actor, $record)) {
return false;
}
if ($record->owner_user_id === null) {
return $this->access->legacyWriteAllowed($actor);
}
return $this->access->scoped($actor, 'publisher', $record->organization_id, $record->opportunity_category)
&& $this->access->operational($actor, 'publish', $record->id) && ! $this->access->conflict($actor, $record)
&& (int) data_get($record->workflow_context, 'approval.actor_id') !== $actor->id
&& (! data_get($record->workflow_context, 'publication_assignment.assignee_user_id')
|| (int) data_get($record->workflow_context, 'publication_assignment.assignee_user_id') === $actor->id);
}
private function authorize(User $actor, Opportunity $record): void
{
abort_unless($this->allowed($actor, $record), 403);
if ($record->owner_user_id !== null) {
app(OpportunityProfileWorkflow::class)->authorizeEditorialCuration($actor, $record);
}
}
/** @return array<string,mixed> */
public function state(User $actor, int $id, int $page = 1): array
{
$record = Opportunity::findOrFail($id);
$this->authorize($actor, $record);
$history = DB::table('audit_events')->where('source_system', 'opportunity-curation')->where('after_data->record_id', $id)
->orderByDesc('id')->paginate(25, ['id', 'actor_user_id', 'occurred_at', 'after_data'], 'page', $page)->through(function ($row) {
$data = json_decode($row->after_data, true, 512, JSON_THROW_ON_ERROR);
return ['id' => $row->id, 'actor' => $row->actor_user_id, 'at' => $row->occurred_at,
'featured' => $data['featured'], 'reason' => Crypt::decryptString($data['reason'])];
});
return ['record' => ['id' => $record->id, 'title' => $record->title, 'featured' => $record->is_featured],
'etag' => $this->management->etag($record), 'history' => $history];
}
/** @param array<string,mixed> $input
* @return array<string,mixed>
*/
public function preview(User $actor, int $id, array $input): array
{
$data = $this->validate($input);
$record = Opportunity::findOrFail($id);
$this->authorize($actor, $record);
$this->check($record, $data);
return ['hash' => $this->hash($actor, $id, $data), 'title' => $record->title,
'before' => $record->is_featured, 'featured' => $data['featured'], 'reason' => $data['reason']];
}
/** @param array<string,mixed> $input
* @return array<string,mixed>
*/
public function confirm(User $actor, int $id, array $input): array
{
$data = $this->validate($input);
Validator::make($input, ['hash' => 'required|string|size:64', 'confirm' => 'required|accepted'])->validate();
abort_unless(hash_equals($this->hash($actor, $id, $data), $input['hash']), 409, 'A conferência mudou; prepare nova prévia.');
return DB::transaction(function () use ($actor, $id, $data) {
$record = Opportunity::whereKey($id)->lockForUpdate()->firstOrFail();
$this->authorize($actor, $record);
$receipt = DB::table('audit_events')->where('source_system', 'opportunity-curation')->where('after_data->record_id', $id)
->where('after_data->client_key', $data['client_key'])->first(['actor_user_id', 'after_data']);
$intent = $this->hash($actor, $id, $data);
if ($receipt) {
$prior = json_decode($receipt->after_data, true, 512, JSON_THROW_ON_ERROR);
abort_unless((int) $receipt->actor_user_id === $actor->id && hash_equals($prior['intent'], $intent), 409, 'Chave já utilizada com outro conteúdo.');
return ['replayed' => true, 'featured' => $record->is_featured, 'etag' => $this->management->etag($record)];
}
$this->check($record, $data);
$before = $record->is_featured;
$record->management_version++;
PortalAudit::save($record, ['is_featured' => $data['featured']], $actor->id);
DB::table('audit_events')->insert(['source_system' => 'opportunity-curation', 'entity_type' => 'opportunities',
'actor_user_id' => $actor->id, 'action' => 'featured_changed', 'data_classification' => 'restricted',
'before_data' => json_encode(['record_id' => $id, 'featured' => $before], JSON_THROW_ON_ERROR),
'after_data' => json_encode(['record_id' => $id, 'featured' => $record->is_featured, 'version' => $record->management_version,
'reason' => Crypt::encryptString($data['reason']), 'client_key' => $data['client_key'], 'intent' => $intent], JSON_THROW_ON_ERROR)]);
return ['replayed' => false, 'featured' => $record->is_featured, 'etag' => $this->management->etag($record)];
});
}
/** @param array<string,mixed> $input
* @return array{etag:string,client_key:string,featured:bool,reason:string}
*/
private function validate(array $input): array
{
$data = Validator::make($input, ['etag' => 'required|string|size:64', 'client_key' => 'required|uuid',
'featured' => 'required|boolean', 'reason' => 'required|string|min:20|max:1000'])->validate();
return ['etag' => $data['etag'], 'client_key' => $data['client_key'],
'featured' => (bool) $data['featured'], 'reason' => $data['reason']];
}
/** @param array{etag:string,client_key:string,featured:bool,reason:string} $data */
private function check(Opportunity $record, array $data): void
{
abort_unless(hash_equals($this->management->etag($record), $data['etag']), 409, 'A oportunidade mudou; recupere a versão actual antes de confirmar.');
abort_if($record->is_featured === $data['featured'], 422, 'O estado de destaque já corresponde à escolha.');
if ($data['featured']) {
abort_unless(Opportunity::published()->whereKey($record->id)->exists(), 409, 'Só uma oportunidade actualmente pública pode entrar em destaque.');
}
}
/** @param array{etag:string,client_key:string,featured:bool,reason:string} $data */
private function hash(User $actor, int $id, array $data): string
{
return hash_hmac('sha256', json_encode([$actor->id, $id, $data], JSON_THROW_ON_ERROR), (string) config('app.key'));
}
}
@@ -16,6 +16,11 @@ class OpportunityProfileWorkflow
public function __construct(private PortalProfileAccess $access, private OpportunityManagement $management,
private OpportunityCanonicalContract $contract, private OpportunityWorkflowTasks $tasks) {}
public function authorizeEditorialCuration(User $actor, Opportunity $record): void
{
$this->authorizeAction($actor, $record, 'publish');
}
/** @return list<string> */
public function actions(User $actor, Opportunity $record): array
{
+2
View File
@@ -22,6 +22,8 @@ class PortalProfileAccess
app_path('Services/PortalProfileAdministration.php'), app_path('Services/PortalSuperusers.php'),
app_path('Services/OpportunityProfileWorkflow.php'), app_path('Http/Controllers/PortalProfileController.php'), app_path('Http/Controllers/DashboardController.php'),
app_path('Http/Controllers/OpportunityManagementController.php'),
app_path('Services/OpportunityCuration.php'), app_path('Http/Controllers/OpportunityCurationController.php'),
resource_path('js/pages/management/opportunity-curation.tsx'),
app_path('Services/OpportunityCanonicalContract.php'), app_path('Services/OpportunityCanonicalResponse.php'), app_path('Services/OpportunityCategoryFields.php'), app_path('Services/OpportunityModularProfiles.php'), app_path('Services/OpportunityWorkflowTasks.php'), base_path('composer.json'), base_path('composer.lock'),
app_path('Services/OpportunityReviewMateriality.php'), app_path('Services/OpportunityReviewConditions.php'), app_path('Services/OpportunityCanonicalMetadata.php'), app_path('Services/OpportunityCompleteness.php'), app_path('Services/OpportunityCanonicalValue.php'),
resource_path('data/investmoz-opportunity-modular.schema.json'), resource_path('js/components/opportunity-canonical-fields.tsx'), resource_path('js/components/opportunity-parcel-geometry.tsx'),
+1 -1
View File
@@ -33,7 +33,7 @@ final class ProactiveInvestment
'proactive.evidence.*.observation_id' => 'sometimes|nullable|string|size:64',
'proactive.supply_comparisons' => 'sometimes|array|max:40',
'proactive.evidence.*.id' => 'required|uuid|distinct',
'proactive.evidence.*.category' => 'required|in:activity,resource,infrastructure,supply,demand',
'proactive.evidence.*.category' => 'required|in:activity,resource,infrastructure,supply,demand,workforce',
'proactive.evidence.*.purpose' => 'required|in:local,methodology',
'proactive.evidence.*.access' => 'required|in:internal,public',
'proactive.evidence.*.document_id' => 'nullable|uuid',
+112
View File
@@ -0,0 +1,112 @@
<?php
namespace App\Services;
use Illuminate\Support\Str;
/** Text matching shared by the database and the built-in page catalogue. */
final class SearchTerms
{
/** @return list<string> */
public function tokens(string $query): array
{
$normalized = mb_strtolower(Str::ascii($query));
$tokens = array_values(array_filter(array_unique(array_map(
fn ($word) => trim($word, ".,;:!?\"'()[]{}"),
preg_split('/\s+/u', trim($normalized), -1, PREG_SPLIT_NO_EMPTY) ?: []
)), fn ($word) => $word !== ''));
if (count($tokens) > 1) {
$meaningful = array_values(array_diff($tokens, ['a', 'as', 'o', 'os', 'de', 'do', 'da', 'dos', 'das', 'em', 'no', 'na', 'nos', 'nas', 'para', 'por', 'e', 'como', 'qual', 'quais', 'um', 'uma']));
return $meaningful === [] ? $tokens : $meaningful;
}
return $tokens;
}
/** Known Portuguese spelling and number variants; do not infer semantic equivalence. */
public function variants(string $token): array
{
foreach ([['sector', 'sectores', 'setor', 'setores'], ['projecto', 'projectos', 'projeto', 'projetos'],
['investir', 'investimento', 'investimentos'], ['investidor', 'investidores'], ['oportunidade', 'oportunidades'], ['documento', 'documentos'],
['empresa', 'empresas'], ['parceria', 'parcerias'], ['zona', 'zonas'], ['formulario', 'formularios'],
['provincia', 'provincias'], ['corredor', 'corredores'], ['cluster', 'clusters'], ['imposto', 'impostos'], ['licenca', 'licencas']] as $group) {
if (in_array($token, $group, true)) {
return $group;
}
}
return [$token];
}
public function pattern(string $token): string
{
return '%'.str_replace(['!', '%', '_'], ['!!', '!%', '!_'], $token).'%';
}
/** Columns are trusted application constants, never user input. */
public function orFields($query, array $columns, string $token): void
{
foreach ($columns as $column) {
foreach ($this->variants($token) as $variant) {
$query->orWhereRaw($this->normalizedColumn($column)." LIKE ? ESCAPE '!'", [$this->pattern($variant)]);
}
}
}
public function constrain($query, array $columns, array $tokens): void
{
foreach ($tokens as $token) {
$query->where(fn ($match) => $this->orFields($match, $columns, $token));
}
}
public function matches(string $text, array $tokens): bool
{
$text = mb_strtolower(Str::ascii($text));
foreach ($tokens as $token) {
if (! array_filter($this->variants($token), fn ($variant) => str_contains($text, $variant))) {
return false;
}
}
return true;
}
private function normalizedColumn(string $column): string
{
return "translate(lower(coalesce({$column}::text, '')), 'áàâãäéèêëíìîïóòôõöúùûüçñ', 'aaaaaeeeeiiiiooooouuuucn')";
}
public function score(string $title, string $phrase, array $tokens): int
{
$title = mb_strtolower(Str::ascii($title));
$phrase = mb_strtolower(Str::ascii(trim($phrase)));
$score = $title === $phrase ? 1000 : (str_contains($title, $phrase) ? 400 : 0);
foreach ($tokens as $token) {
if ($this->matches($title, [$token])) {
$score += 20;
}
}
return $score;
}
/** The same title score used for in-memory pages, with bound search terms. */
public function orderByRelevance($query, string $titleColumn, string $phrase, array $tokens, string $tieColumn): void
{
$column = $this->normalizedColumn($titleColumn);
$phrase = mb_strtolower(Str::ascii(trim($phrase)));
$sql = "CASE WHEN {$column} = ? THEN 1000 WHEN {$column} LIKE ? ESCAPE '!' THEN 400 ELSE 0 END";
$bindings = [$phrase, $this->pattern($phrase)];
foreach ($tokens as $token) {
$conditions = [];
foreach ($this->variants($token) as $variant) {
$conditions[] = "{$column} LIKE ? ESCAPE '!'";
$bindings[] = $this->pattern($variant);
}
$sql .= ' + CASE WHEN ('.implode(' OR ', $conditions).') THEN 20 ELSE 0 END';
}
$query->orderByRaw('('.$sql.') DESC', $bindings)->orderBy($titleColumn)->orderBy($tieColumn);
}
}
+16 -9
View File
@@ -74,7 +74,7 @@ final class SectorInvestment
}
}
return ['articles' => $articles, 'relations' => collect($relations)->unique('id')->values()->all(), 'locations' => $locations,
return ['resource_reference' => isset(SectorResourceReferences::SOURCES[$sector->slug]) ? app(SectorResourceReferences::class)->view($sector->slug) : null, 'articles' => $articles, 'relations' => collect($relations)->unique('id')->values()->all(), 'locations' => $locations,
'start_href' => app(InvestorPath::class)->available() ? '/investimentos/a-partir-do-setor/'.$sector->slug : null,
'start_notice' => app(InvestorPath::class)->available() ? null : 'O percurso para guardar a ideia está temporariamente indisponível. A consulta e o apoio continuam disponíveis.',
'register_href' => app(PortalProfileAccess::class)->available() ? ($creation ? $creation.'?sector_id='.$sector->id : '/perfil/promotor') : null,
@@ -114,24 +114,31 @@ final class SectorInvestment
*/
public function attach(User $actor, string $id, array $input): void
{
$data = Validator::make($input, ['version' => 'required|integer|min:1', 'sector_slug' => 'required|string|max:150'])->validate();
$data = Validator::make($input, ['version' => 'required|integer|min:1', 'sector_slug' => 'required|string|max:150', 'sector_reference' => 'nullable|array'])->validate();
$context = $this->context($data['sector_slug']);
abort_if(! empty($data['sector_reference']) && ! isset(SectorResourceReferences::SOURCES[$context['slug']]), 422);
$reference = isset($data['sector_reference']) ? app(SectorResourceReferences::class)->selection($context['slug'], $data['sector_reference']) : null;
abort_unless(app(InvestorPath::class)->available(), 404);
DB::transaction(function () use ($actor, $id, $data, $context) {
DB::transaction(function () use ($actor, $id, $data, $context, $reference) {
$desk = app(VirtualDesk::class);
$case = $desk->case($actor, $id, true, true);
abort_if($case->closed_at, 409, 'O dossier está encerrado.');
abort_unless(DB::table('investor_journeys')->where('id', $case->journey_id)->whereNotNull('path_definition_id')->whereNull('deleted_at')->exists(), 409, 'Este dossier conserva outro roteiro.');
$payload = $desk->decode($case->payload);
foreach ($payload['investor_path']['sector_contexts'] ?? [] as $existing) {
if ($existing['id'] === $context['id']) {
return;
}
$hasSector = array_any($payload['investor_path']['sector_contexts'] ?? [], fn ($row) => $row['id'] === $context['id']);
$hasReference = $reference !== null && array_any($payload['investor_path']['sector_references'] ?? [], fn ($row) => $row['key'] === $reference['key']);
if ($hasSector && ($reference === null || $hasReference)) {
return;
}
abort_unless((int) $case->version === $data['version'], 409, 'O dossier foi alterado; recupere a versão atual.');
$payload['investor_path']['sector_contexts'][] = [...$context, 'actor_id' => $actor->id, 'saved_at' => now()->toIso8601String()];
if (! $hasSector) {
$payload['investor_path']['sector_contexts'][] = [...$context, 'actor_id' => $actor->id, 'saved_at' => now()->toIso8601String()];
}
if ($reference !== null && ! $hasReference) {
$payload['investor_path']['sector_references'][] = [...$reference, 'actor_id' => $actor->id, 'saved_at' => now()->toIso8601String()];
}
DB::table('desk_cases')->where('id', $id)->update(['payload' => $desk->encode($payload), 'version' => $case->version + 1, 'updated_at' => now()]);
$desk->event($desk->case($actor, $id), $actor, 'sector_context_referenced', ['sector_id' => $context['id'], 'sector_slug' => $context['slug']]);
$desk->event($desk->case($actor, $id), $actor, 'sector_context_referenced', ['sector_id' => $context['id'], 'sector_slug' => $context['slug'], 'sector_reference_key' => $reference['key'] ?? null]);
});
}
}
+82
View File
@@ -0,0 +1,82 @@
<?php
namespace App\Services;
use App\Support\CheckedFile;
use Illuminate\Support\Facades\Validator;
/** Versioned reference content over existing sector pages and private dossiers. */
final class SectorResourceReferences
{
public const SOURCES = [
'mineracao' => '7202fb5986b40e62347867c653770030683ffec2701a2921e84342f15e9f3d8a',
'energia-especifica' => '2d9d94ac70115ade43c4093a4d5b302aaa93623a149f936615c38ba67b1e2bd2',
];
// Generated data are checked independently from the unchanged Word originals.
public const DATA = [
'mineracao' => ['file' => 'mining-reference-20261111.json', 'hash' => '9274f4059ef5d7aa8ec625f1c6f8fd4a11c8e314c5282c5f24c38dbcf7c68bb5'],
'energia-especifica' => ['file' => 'energy-reference-20261111.json', 'hash' => '3c0fe65a0682d952987f63bb48aaaada9ba4f8d10ae40149658211f56fa8e7ac'],
];
/** @return array<string,mixed> */
public function data(string $sector): array
{
abort_unless(isset(self::DATA[$sector]), 404);
$path = database_path('data/'.self::DATA[$sector]['file']);
abort_unless(hash_equals(self::DATA[$sector]['hash'], CheckedFile::sha256($path)), 503, 'A versão das referências sectoriais mudou.');
$data = json_decode(CheckedFile::read($path), true, 512, JSON_THROW_ON_ERROR);
abort_unless(($data['source_sha256'] ?? null) === self::SOURCES[$sector], 503, 'Reconciliar a versão da referência antes de usar.');
return $data;
}
/** @return array<string,mixed> */
public function view(string $sector): array
{
$data = $this->data($sector);
$mining = $sector === 'mineracao';
$groups = $mining ? $data['minerals'] : $data['groups'];
$risks = $data['risks'];
if ($mining) {
$start = array_find_key($risks, fn ($text) => str_starts_with($text, 'Riscos e pontos'));
$end = array_find_key($risks, fn ($text) => str_starts_with($text, 'Próximos passos'));
$risks = $start !== null && $end !== null ? array_slice($risks, $start + 1, $end - $start - 1) : [];
}
return ['sector_slug' => $sector, 'title' => $mining ? 'Mineração e cadeias de valor' : 'Energia e cadeias de valor',
'introduction' => $mining ? 'Explore recursos minerais por localização e possibilidades de fornecimento, logística, energia e transformação. São referências para preparar o investimento; uma ocorrência não identifica uma concessão ou um projecto disponível.' : 'Explore gás natural, electricidade, renováveis e as actividades da cadeia de valor. Distinga projectos de referência, propostas, previsões e possibilidades de fornecimento ao preparar o investimento.',
'group_label' => $mining ? 'Grupo mineral' : 'Segmento energético',
'resource_heading' => $mining ? 'Recursos e localização' : 'Segmentos e âmbito territorial',
'resource_notice' => $mining ? 'A localização é a indicada para o grupo de minérios. Quando a fonte não discrimina a distribuição de cada minério, não se presume que todos ocorram em todos os locais.' : 'Bacias, localidades, corredores e províncias conservam o âmbito indicado na fonte. Uma localização não delimitada fica por definir no investimento; uma referência não significa concurso aberto ou projecto disponível.',
'source_sha256' => self::SOURCES[$sector], 'reference_date' => $data['reference_date'],
'groups' => array_map(fn ($group) => [...array_diff_key($group, ['province_names' => true]), 'location_labels' => $group['location_labels'] ?? $group['province_names']], $groups),
'areas' => array_map(fn ($area) => [...array_diff_key($area, ['source_fields' => true, 'province_names' => true, 'mineral_ids' => true]), 'location_labels' => $area['location_labels'] ?? $area['province_names'], 'group_ids' => $area['group_ids'] ?? $area['mineral_ids']], $data['areas']),
'projects' => $data['projects'], 'indicators' => array_map(fn ($row) => ['label' => $row['mineral'], 'value' => $row['value'], 'source' => $row['source']], $data['indicators']),
'risks' => $risks, 'sources' => $data['sources'], 'source_links' => $data['source_links'] ?? [], 'background' => $data['background'] ?? []];
}
/** @param array<string,mixed> $input
* @return array<string,mixed> */
public function selection(string $sector, array $input): array
{
abort_unless(isset(self::SOURCES[$sector]), 422);
$input = Validator::make($input, ['area_id' => 'required|string', 'source_sha256' => 'required|string|size:64', 'location_label' => 'nullable|string|max:150', 'group_id' => 'nullable|string'])->validate();
abort_unless(hash_equals(self::SOURCES[$sector], $input['source_sha256']), 409, 'A referência foi actualizada; recupere a selecção actual.');
$data = $this->view($sector);
$area = array_find($data['areas'], fn ($row) => $row['id'] === $input['area_id']);
abort_unless($area, 422, 'Escolha uma área de referência existente.');
$group = isset($input['group_id']) ? array_find($data['groups'], fn ($row) => $row['id'] === $input['group_id']) : null;
abort_if(isset($input['group_id']) && (! $group || ! in_array($group['id'], $area['group_ids'], true)), 422, 'O grupo escolhido não corresponde à cadeia de valor.');
$location = $input['location_label'] ?? null;
abort_if($location !== null && ! in_array($location, $area['location_labels'], true), 422, 'A localização escolhida não consta desta área de referência.');
$snapshot = ['sector_slug' => $sector, 'source_sha256' => self::SOURCES[$sector], 'reference_date' => $data['reference_date'],
'group_id' => $group['id'] ?? null, 'group_name' => $group['name'] ?? null, 'area_id' => $area['id'], 'title' => $area['title'], 'location_label' => $location,
'locations_text' => $area['locations_text'], 'indicative_location' => $area['indicative_location'], 'value_chain' => $area['value_chain'], 'market_evidence' => $area['market_evidence'],
'source_row' => $area['source_row'], 'source_state' => $area['source_state'], 'coordinates' => null, 'promotion_ready' => false,
'territory_precision' => $location === null ? 'source_area_reference' : ($location === 'Maputo' ? 'province_or_city_unspecified' : 'source_location_reference'),
'href' => '/sectores/'.$sector.'?'.http_build_query(['resource_area' => $area['id'], 'resource_location' => $location, 'resource_group' => $group['id'] ?? null])];
return ['key' => hash('sha256', json_encode([$sector, self::SOURCES[$sector], $area['id'], $location, $group['id'] ?? null], JSON_THROW_ON_ERROR)), ...$snapshot];
}
}
+17 -7
View File
@@ -8,15 +8,20 @@ use Illuminate\Support\Facades\DB;
final class TerritoryGeometry
{
/** @return array<string,mixed> */
public function context(string $id, bool $management = false): array
public function context(string $id, bool $management = false, bool $includeChildren = false): array
{
$geography = app(InvestmentGeography::class);
abort_unless($geography->references($management)->where('id', $id)->exists(), 404);
$selected = $geography->references($management)->where('id', $id)->first(['location_type']);
abort_unless($selected !== null, 404);
$path = $geography->path($id, $management);
$ids = array_map(fn ($node) => $node->id, $path);
$ids = array_values(array_unique([$id, ...array_map(fn ($node) => $node->id, $path)]));
$childType = ['country' => 'province', 'province' => 'district', 'district' => 'admin_post', 'admin_post' => 'locality'][$selected->location_type] ?? null;
$children = $geography->references($management)->where('parent_id', $id)->where('location_type', $childType ?? 'none');
$childCount = $includeChildren && $childType ? (clone $children)->count() : 0;
$childIds = $includeChildren && $childType ? $children->orderBy('name')->orderBy('id')->limit(500)->pluck('id')->all() : [];
$approved = app(ApprovedAdministrativeBoundaries::class);
$rows = DB::table('geo_geometries as g')->join('geo_locations as l', 'l.id', '=', 'g.geo_location_id')
->whereIn('g.geo_location_id', $ids)->whereNull('g.deleted_at')->whereNull('l.deleted_at')
->whereIn('g.geo_location_id', [...$ids, ...$childIds])->whereNull('g.deleted_at')->whereNull('l.deleted_at')
->where(function ($query) use ($management, $approved) {
$query->where('g.data_classification', 'public');
$query->orWhere(function ($internal) use ($management, $approved) {
@@ -29,20 +34,22 @@ final class TerritoryGeometry
->whereRaw("g.properties->>'purpose' = 'administrative_boundary'")
->whereIn('g.geometry_type', ['polygon', 'multipolygon'])
->whereRaw('ST_IsValid(g.geom) AND NOT ST_IsEmpty(g.geom) AND ST_SRID(g.geom)=4326')
->orderByRaw('CASE WHEN g.geo_location_id = ? THEN 0 WHEN g.geo_location_id IN ('.implode(',', array_fill(0, count($ids), '?')).') THEN 1 ELSE 2 END', [$id, ...$ids])
->orderBy('g.geo_location_id')->orderBy('g.id')->limit(101)
->get(['g.id', 'g.geo_location_id', 'l.name', 'l.location_type', 'g.properties', 'g.data_classification',
DB::raw('ST_AsGeoJSON(ST_SimplifyPreserveTopology(g.geom,0.0001),6) as geometry'),
DB::raw("encode(sha256(ST_AsEWKB(g.geom)),'hex') as geometry_sha256"),
DB::raw("encode(sha256(convert_to(g.properties::text,'UTF8')),'hex') as properties_sha256")]);
$queryTruncated = $rows->count() > 100;
if (! $management) {
$rows = $rows->filter(fn ($row) => $row->data_classification === 'public'
|| $approved->allows($row->id, $row->geo_location_id, $row->geometry_sha256, $row->properties_sha256))->values();
}
$areas = $rows->take(100)->map(function ($row) use ($id, $management) {
$areas = $rows->take(100)->map(function ($row) use ($id, $management, $childIds) {
$source = json_decode($row->properties, true, 512, JSON_THROW_ON_ERROR);
return ['id' => $row->id, 'territory_id' => $row->geo_location_id, 'location_type' => $row->location_type,
'selected_territory' => $row->geo_location_id === $id, 'geometry' => json_decode($row->geometry, true, 512, JSON_THROW_ON_ERROR),
'selected_territory' => $row->geo_location_id === $id, 'child_territory' => in_array($row->geo_location_id, $childIds, true), 'geometry' => json_decode($row->geometry, true, 512, JSON_THROW_ON_ERROR),
'title' => 'Limite administrativo: '.$row->name, 'purpose' => 'administrative_boundary',
'source_reference' => $source['authority'] ?? null, 'source_date' => $source['source_version'] ?? null,
'source_url' => $source['source_url'] ?? null, 'source_sha256' => $source['source_sha256'] ?? null,
@@ -51,6 +58,9 @@ final class TerritoryGeometry
return ['territory_id' => $id, 'territory_registered' => true,
'selected_geometry_available' => $rows->contains('geo_location_id', $id), 'context_geometry_count' => count($areas),
'areas' => $areas, 'truncated' => $rows->count() > 100, 'investment_site' => false];
'areas' => $areas, 'truncated' => $queryTruncated || $childCount > 500, 'investment_site' => false,
'child_coverage' => $includeChildren ? ['location_type' => $childType, 'registered_count' => $childCount,
'represented_count' => count(array_unique(array_column(array_filter($areas, fn ($area) => $area['child_territory']), 'territory_id'))),
'reference_limit' => 500, 'geometry_limit' => 100] : null];
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
namespace App\Support;
use Generator;
final class PublicSitemapXml
{
/** @param iterable<string> $locations
* @return Generator<int, string>
*/
public function chunks(iterable $locations, bool $index = false): Generator
{
$root = $index ? 'sitemapindex' : 'urlset';
$item = $index ? 'sitemap' : 'url';
yield '<?xml version="1.0" encoding="UTF-8"?>'."\n";
yield '<'.$root.' xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">'."\n";
foreach ($locations as $location) {
yield '<'.$item.'><loc>'.htmlspecialchars($location, ENT_XML1 | ENT_QUOTES, 'UTF-8').'</loc></'.$item.'>'."\n";
}
yield '</'.$root.'>';
}
}
+1 -1
View File
@@ -21,7 +21,7 @@ return [
// Experimental proposal only; functional approval and operational SLA remain pending.
'opportunity_modular_profiles_qa_enabled' => env('PORTAL_OPPORTUNITY_MODULAR_PROFILES_QA_ENABLED', false),
'opportunity_other_qa_subtypes' => [],
'operator_name' => env('PORTAL_OPERATOR_NAME'),
'operator_name' => env('PORTAL_OPERATOR_NAME') ?: 'Tempo, Lda.',
// Tempo, 19/10/2026: institutional contact; commercial department remains separate.
'institutional_contact_email' => env('PORTAL_INSTITUTIONAL_CONTACT_EMAIL', 'comercial@tempo.co.mz'),
'support' => [
+6
View File
@@ -28,6 +28,12 @@ return [
|
*/
'translation' => [
'url' => env('LIBRETRANSLATE_URL', 'http://127.0.0.1:5000'),
'timeout' => (int) env('LIBRETRANSLATE_TIMEOUT', 60),
'api_key' => env('LIBRETRANSLATE_API_KEY'),
],
'postmark' => [
'key' => env('POSTMARK_API_KEY'),
],
@@ -0,0 +1,807 @@
{
"format": "investmoz-energy-reference-v1",
"source_sha256": "2d9d94ac70115ade43c4093a4d5b302aaa93623a149f936615c38ba67b1e2bd2",
"source_document_name": "Oportunidades de investimento em energia.docx",
"reference_date": "2026-11-11",
"title": "Energia e cadeias de valor em Moçambique",
"groups": [
{
"id": "EN-GAS",
"name": "Gás natural e GNL",
"locations_text": "Bacia do Rovuma; Buzi. A fonte não atribui todas as áreas a uma província. Projectos de GNL; base logística de Pemba. Gasoduto Norte–Sul; corredor Palma–Maputo, apresentado como proposta de estudo. Corredor Palma–Maputo proposto no estudo; implantação de cada indústria por definir. Implantação a definir no investimento; sem localização discriminada na fonte.",
"location_labels": [
"Bacia do Rovuma",
"Buzi",
"Pemba",
"Norte–Sul",
"Palma–Maputo"
],
"coordinates": null
},
{
"id": "EN-POWER",
"name": "Electricidade, hidroeléctricas e redes",
"locations_text": "Rio Zambeze; Maputo–Temane; interligação Moçambique–Zâmbia. Âmbitos de projecto, sem coordenadas.",
"location_labels": [
"Rio Zambeze",
"Maputo–Temane",
"Moçambique–Zâmbia"
],
"coordinates": null
},
{
"id": "EN-SOLAR",
"name": "Solar, armazenamento e sistemas fora da rede",
"locations_text": "Mocuba (Zambézia), Metoro (Cabo Delgado), Cuamba (Niassa) e Afungi. Sistemas fora da rede: implantação a definir.",
"location_labels": [
"Zambézia",
"Mocuba",
"Cabo Delgado",
"Metoro",
"Niassa",
"Cuamba",
"Afungi"
],
"coordinates": null
},
{
"id": "EN-WIND",
"name": "Energia eólica",
"locations_text": "Namaacha (Maputo, sem distinção entre província e cidade na fonte).",
"location_labels": [
"Namaacha",
"Maputo"
],
"coordinates": null
},
{
"id": "EN-HYDROGEN",
"name": "Hidrogénio verde",
"locations_text": "Sem implantação concreta identificada na fonte.",
"location_labels": [],
"coordinates": null
},
{
"id": "EN-BIO",
"name": "Biocombustíveis",
"locations_text": "Implantação a definir; a fonte não discrimina locais de produção.",
"location_labels": [],
"coordinates": null
},
{
"id": "EN-GREEN",
"name": "Parques industriais verdes",
"locations_text": "Corredores e parques sem implantação discriminada na fonte.",
"location_labels": [],
"coordinates": null
}
],
"areas": [
{
"id": "ENE-01",
"title": "Gás natural — Upstream",
"group_ids": [
"EN-GAS"
],
"locations_text": "Bacia do Rovuma; Buzi. A fonte não atribui todas as áreas a uma província.",
"location_labels": [
"Bacia do Rovuma",
"Buzi"
],
"value_chain": "Concursos de áreas, campos maduros (Buzi), parcerias com empresas locais. Perfil de investidor: Operadores e parceiros locais.",
"market_evidence": "A bacia do Rovuma concentra as maiores oportunidades do país. O Mozambique LNG (Área 1) prevê uma capacidade anual de 12,9 milhões de toneladas; o Coral Norte FLNG (Eni) teve decisão final de investimento em 2025, com 3,6 milhões de toneladas anuais a partir de 2028; e o Rovuma LNG (ExxonMobil), estimado em cerca de 24 mil milhões de dólares, aproxima-se da decisão final de investimento. Para investidores e empresas nacionais, as oportunidades estendem-se a engenharia, construção, logística, serviços marítimos, telecomunicações e manutenção industrial. No mercado interno, o Instituto Nacional de Petróleo identifica áreas como o gasoduto Norte-Sul, o campo maduro de Buzi, o GNL de pequena escala, o armazenamento de GPL e a base logística de Pemba.",
"indicative_location": "Bacia do Rovuma; Buzi. A fonte não atribui todas as áreas a uma província.",
"source_row": "table4:row1; paragraph:34,paragraph:35",
"source_state": "Referência para preparação",
"availability": "reference_only",
"promotion_ready": false,
"coordinates": null
},
{
"id": "ENE-02",
"title": "Gás natural — Construção e serviços",
"group_ids": [
"EN-GAS"
],
"locations_text": "Projectos de GNL; base logística de Pemba.",
"location_labels": [
"Pemba"
],
"value_chain": "Engenharia, logística, serviços marítimos, telecomunicações, manutenção, formação. Perfil de investidor: PME nacionais e parcerias internacionais.",
"market_evidence": "Para investidores e empresas nacionais, as oportunidades estendem-se a engenharia, construção, logística, serviços marítimos, telecomunicações e manutenção industrial. No mercado interno, o Instituto Nacional de Petróleo identifica áreas como o gasoduto Norte-Sul, o campo maduro de Buzi, o GNL de pequena escala, o armazenamento de GPL e a base logística de Pemba. Está prevista uma componente de formação para 15.734 moçambicanos (custo estimado em 126 milhões de dólares) e a capacitação de 355 pequenas e médias empresas na fase de construção e cerca de 1.500 na fase operacional. O ministro apelou a parcerias com entidades estrangeiras especializadas para transferência de conhecimento e tecnologia.",
"indicative_location": "Projectos de GNL; base logística de Pemba.",
"source_row": "table4:row2; paragraph:35,paragraph:58",
"source_state": "Referência para preparação",
"availability": "reference_only",
"promotion_ready": false,
"coordinates": null
},
{
"id": "ENE-03",
"title": "Gás natural — Midstream",
"group_ids": [
"EN-GAS"
],
"locations_text": "Gasoduto Norte–Sul; corredor Palma–Maputo, apresentado como proposta de estudo.",
"location_labels": [
"Norte–Sul",
"Palma–Maputo"
],
"value_chain": "Gasodutos Norte-Sul e Palma–Maputo, armazenamento de GPL, GNL de pequena escala. Perfil de investidor: Investidores em infra-estruturas.",
"market_evidence": "Para investidores e empresas nacionais, as oportunidades estendem-se a engenharia, construção, logística, serviços marítimos, telecomunicações e manutenção industrial. No mercado interno, o Instituto Nacional de Petróleo identifica áreas como o gasoduto Norte-Sul, o campo maduro de Buzi, o GNL de pequena escala, o armazenamento de GPL e a base logística de Pemba. Um estudo do Programa de Partilha de Conhecimentos (KSP) propõe um corredor energético Palma–Maputo para integrar petroquímica, fertilizantes e geração eléctrica, e destaca o gás natural veicular (GNV) nos transportes.",
"indicative_location": "Gasoduto Norte–Sul; corredor Palma–Maputo, apresentado como proposta de estudo.",
"source_row": "table4:row3; paragraph:35,paragraph:51",
"source_state": "Referência para preparação",
"availability": "reference_only",
"promotion_ready": false,
"coordinates": null
},
{
"id": "ENE-04",
"title": "Gás natural — Industrialização",
"group_ids": [
"EN-GAS"
],
"locations_text": "Corredor Palma–Maputo proposto no estudo; implantação de cada indústria por definir.",
"location_labels": [
"Palma–Maputo"
],
"value_chain": "Fertilizantes, petroquímica, geração eléctrica a gás. Perfil de investidor: Grupos industriais e produtores independentes de energia.",
"market_evidence": "O Governo defende que o gás natural não deve ser visto apenas como produto de exportação, mas como matéria-prima para criar indústrias, empresas e emprego, com usos que vão da electricidade aos fertilizantes. O ministro dos Recursos Minerais e Energia identificou como desafios do sector privado o acesso a financiamento, a certificação e a tecnologia. Um estudo do Programa de Partilha de Conhecimentos (KSP) propõe um corredor energético Palma–Maputo para integrar petroquímica, fertilizantes e geração eléctrica, e destaca o gás natural veicular (GNV) nos transportes. Fertilizantes: o grupo Export Trading Group (ETG) manifestou, em Outubro de 2026, interesse em produzir fertilizantes a partir do gás natural, com o objectivo de liderar a produção na África Subsariana.",
"indicative_location": "Corredor Palma–Maputo proposto no estudo; implantação de cada indústria por definir.",
"source_row": "table4:row4; paragraph:50,paragraph:51,paragraph:53",
"source_state": "Referência para preparação",
"availability": "reference_only",
"promotion_ready": false,
"coordinates": null
},
{
"id": "ENE-05",
"title": "Gás natural — Mercado final",
"group_ids": [
"EN-GAS"
],
"locations_text": "Implantação a definir no investimento; sem localização discriminada na fonte.",
"location_labels": [],
"value_chain": "GNV, biocombustíveis, mobilidade eléctrica. Perfil de investidor: Empresas de distribuição e mobilidade.",
"market_evidence": "Gás natural veicular e biocombustíveis: o Governo considera GNV, conversão de gás em electricidade, mobilidade eléctrica e biocombustíveis respostas à volatilidade dos preços dos combustíveis. Mercado interno: gasodutos e projectos de GNV orientados para o consumo doméstico abrem espaço ao sector privado.",
"indicative_location": "Implantação a definir no investimento; sem localização discriminada na fonte.",
"source_row": "table4:row5; paragraph:55,paragraph:56",
"source_state": "Referência para preparação",
"availability": "reference_only",
"promotion_ready": false,
"coordinates": null
},
{
"id": "ENE-06",
"title": "Hidroeléctricas, redes e interligações",
"group_ids": [
"EN-POWER"
],
"locations_text": "Rio Zambeze; Maputo–Temane; interligação Moçambique–Zâmbia. Âmbitos de projecto, sem coordenadas.",
"location_labels": [
"Rio Zambeze",
"Maputo–Temane",
"Moçambique–Zâmbia"
],
"value_chain": "Fornecimento de subestações, transformadores, protecções eléctricas, SCADA e modernização de redes.",
"market_evidence": "A Hidroeléctrica de Cahora Bassa, com 2.075 MW instalados, mantém um papel central. Estão em preparação a barragem de Mphanda Nkuwa (1.500 MW no rio Zambeze, com conclusão prevista para 2031), a expansão de Cahora Bassa Norte (1.250 MW) e a linha de transmissão Maputo-Temane. A modernização da rede da EDM e a interligação Moçambique-Zâmbia de 400 kV, apoiada pelo BAD, abrem espaço para fornecimento de subestações, transformadores, protecções eléctricas e sistemas SCADA, além de reforçar a exportação regional de energia. Hidroeléctrica: a ETS prevê 3,5 GW de nova capacidade hídrica entre 2024 e 2030, incluindo a conclusão de Mphanda Nkuwa (1.500 MW), um projecto que o Governo procurou apresentar a parceiros estratégicos. Solar e eólica: a ETS prevê um programa de leilões de energia renovável para impulsionar estas fontes, bem como a expansão e modernização da rede para absorver a nova geração.",
"indicative_location": "Rio Zambeze; Maputo–Temane; interligação Moçambique–Zâmbia. Âmbitos de projecto, sem coordenadas.",
"source_row": "paragraphs:37,38,67,68",
"source_state": "Referência para preparação",
"availability": "reference_only",
"promotion_ready": false,
"coordinates": null
},
{
"id": "ENE-07",
"title": "Solar, baterias e sistemas fora da rede",
"group_ids": [
"EN-SOLAR"
],
"locations_text": "Mocuba (Zambézia), Metoro (Cabo Delgado), Cuamba (Niassa) e Afungi. Sistemas fora da rede: implantação a definir.",
"location_labels": [
"Zambézia",
"Mocuba",
"Cabo Delgado",
"Metoro",
"Niassa",
"Cuamba",
"Afungi"
],
"value_chain": "Produção solar, sistemas de armazenamento, fornecimento e manutenção de equipamentos e soluções fora da rede.",
"market_evidence": "Moçambique recebe cerca de 2.500 horas de sol por ano e dispõe de potencial eólico ao longo da costa. A estratégia nacional prevê 2 GW de energia solar até 2030, e já existem projectos como a central solar de 7,1 MW associada ao complexo de Afungi e um parque solar de 400 MW ligado a Cahora Bassa. Os sistemas fora da rede permitem ainda alargar o acesso à electricidade onde a rede é mais lenta ou dispendiosa de expandir. Os projectos abaixo estão documentados em fontes oficiais (MIREME e EDM) ou em comunicados dos promotores. Mostram o modelo dominante: produtores independentes em parceria com a EDM, com contratos de compra de energia de longo prazo e financiamento de instituições de desenvolvimento. Os dados de Metoro diferem entre fontes, sobretudo no valor do investimento, por isso devem ser confirmados com a EDM. Em Mocuba, parte da receita anual destina-se a programas sociais na comunidade, e a Cuamba é um dos primeiros projectos do país a integrar armazenamento em baterias, o que sugere oportunidades em sistemas de armazenamento. Estado actual: segundo o director-executivo da Globeleq em Moçambique, as centrais de Mocuba e Cuamba estão em operação, foram adquiridas pela empresa em Dezembro de 2023 e injectam 48 MW na rede da EDM (33 MW em Mocuba e 15 MW em Cuamba), reduzindo as perdas de transmissão nas regiões centro e norte. Esta informação vem de uma declaração do promotor divulgada pela imprensa; a referência consultada não apresenta confirmação directa da EDM nem dados de 2026 sobre Metoro .",
"indicative_location": "Mocuba (Zambézia), Metoro (Cabo Delgado), Cuamba (Niassa) e Afungi. Sistemas fora da rede: implantação a definir.",
"source_row": "paragraphs:40,43,45,46; table3:rows1–3",
"source_state": "Referência para preparação",
"availability": "reference_only",
"promotion_ready": false,
"coordinates": null
},
{
"id": "ENE-08",
"title": "Energia eólica — Namaacha",
"group_ids": [
"EN-WIND"
],
"locations_text": "Namaacha (Maputo, sem distinção entre província e cidade na fonte).",
"location_labels": [
"Namaacha",
"Maputo"
],
"value_chain": "Desenvolvimento e fornecimento para geração eólica; obras previstas dependem do fecho financeiro.",
"market_evidence": "Quanto à eólica, a Globeleq indicou em 2026 que desenvolve, com a EDM e a Source Energia, o parque eólico de Namaacha (Maputo), com 120 MW previstos e investimento global estimado em cerca de 260 milhões de dólares. O início das obras está previsto para 2027, depois do fecho financeiro, pelo que o projecto ainda não está em construção.",
"indicative_location": "Namaacha (Maputo, sem distinção entre província e cidade na fonte).",
"source_row": "paragraph:47",
"source_state": "Referência para preparação",
"availability": "reference_only",
"promotion_ready": false,
"coordinates": null
},
{
"id": "ENE-09",
"title": "Hidrogénio verde — estudos e infra-estruturas",
"group_ids": [
"EN-HYDROGEN"
],
"locations_text": "Sem implantação concreta identificada na fonte.",
"location_labels": [],
"value_chain": "Estudos de mercado e viabilidade, consultoria, parcerias, produção, armazenamento e transporte.",
"market_evidence": "A Estratégia de Transição Energética (ETS) prevê investimentos de cerca de 80 mil milhões de dólares até 2050 e fixa a meta de posicionar Moçambique entre os líderes na produção de hidrogénio na África austral até 2030. O Governo aponta a combinação de recursos hídricos, gás, solar e eólico como base para a produção, sobretudo de hidrogénio verde, e prevê que seja maioritariamente exportado. A ETS refere ainda a necessidade de instalações de produção, armazenamento e redes de transporte, e parcerias regionais. O sector está numa fase inicial: o banco alemão KfW e a EDM assinaram, em Março de 2025, um acordo de 500 mil euros para estudos de mercado e de viabilidade técnica e económica do hidrogénio verde. Existem, por isso, oportunidades em estudos, consultoria, parcerias e infra-estruturas de apoio, mas o levantamento não identifica projectos com decisão final de investimento.",
"indicative_location": "Sem implantação concreta identificada na fonte.",
"source_row": "paragraphs:61,62",
"source_state": "Referência para preparação",
"availability": "reference_only",
"promotion_ready": false,
"coordinates": null
},
{
"id": "ENE-10",
"title": "Biocombustíveis e respectivas cadeias de fornecimento",
"group_ids": [
"EN-BIO"
],
"locations_text": "Implantação a definir; a fonte não discrimina locais de produção.",
"location_labels": [],
"value_chain": "Matérias-primas agrícolas, produção, armazenagem, transporte, distribuição e mistura de biocombustíveis.",
"market_evidence": "O Decreto n.º 61/2023 regula a produção, transporte, armazenagem, distribuição, comercialização e exportação de biocombustíveis e prevê que importadores e distribuidores de combustíveis líquidos adquiram biocombustível produzido no país para mistura. A produção acima de 1.000.000 de litros por ano é licenciada pelo ministro da Energia. Um estudo do programa USAID SPEED indica que as misturas B3 (biodiesel) e E10 (etanol) estavam previstas para iniciar em Agosto de 2024 e que as matérias-primas imediatamente disponíveis são a cana-de-açúcar (etanol) e o óleo de soja e de algodão (biodiesel). O mesmo estudo lembra que a anterior tentativa de criar a indústria (2005–2013, centrada na jatrofa e na exportação) não teve sucesso, por falta de sementes de qualidade, apoio à cadeia de valor e mercado, e que há preocupações com terra, água e segurança alimentar. Em Junho de 2026, o MIREME promoveu o primeiro seminário nacional de biocombustíveis para identificar instrumentos de financiamento e operacionalizar a mistura obrigatória.",
"indicative_location": "Implantação a definir; a fonte não discrimina locais de produção.",
"source_row": "paragraphs:64,65",
"source_state": "Referência para preparação",
"availability": "reference_only",
"promotion_ready": false,
"coordinates": null
},
{
"id": "ENE-11",
"title": "Parques industriais verdes",
"group_ids": [
"EN-GREEN"
],
"locations_text": "Corredores e parques sem implantação discriminada na fonte.",
"location_labels": [],
"value_chain": "Indústria e infra-estruturas abastecidas por energia limpa; preparação de parcerias e localização.",
"market_evidence": "Parques industriais verdes: a ETS refere corredores e parques industriais abastecidos por energia limpa, uma oportunidade para indústria e infra-estruturas.",
"indicative_location": "Corredores e parques sem implantação discriminada na fonte.",
"source_row": "paragraph:69",
"source_state": "Referência para preparação",
"availability": "reference_only",
"promotion_ready": false,
"coordinates": null
}
],
"projects": [
"Mozambique LNG (TotalEnergies) — Gás natural. Investimento estimado: ~20 mil milhões USD. Estado/calendário na fonte: Relançado em Jan. 2026; primeira produção em 2029.",
"Rovuma LNG (ExxonMobil) — Gás natural. Investimento estimado: ~24 mil milhões USD. Estado/calendário na fonte: Pré-decisão final de investimento.",
"Coral Norte FLNG (Eni) — Gás natural. Investimento estimado: 6 a 7 mil milhões USD. Estado/calendário na fonte: Decisão final tomada em 2025; capacidade a partir de 2028.",
"Mphanda Nkuwa — Hidroeléctrica. Investimento estimado: ~4,5 mil milhões EUR. Estado/calendário na fonte: 1.500 MW; conclusão prevista para 2031.",
"Interligação Moçambique-Zâmbia — Transmissão. Estado/calendário na fonte: Em execução, com apoio do BAD.",
"Central solar de Afungi — Solar. Investimento estimado: 10 a 15 milhões USD. Estado/calendário na fonte: Concurso EPC lançado (7,1 MW).",
"Central Solar de Mocuba (Zambézia) — Solar. Capacidade: 40 MW instalados (cerca de 33 MW injectados na rede); cerca de 79 GWh /ano. Investimento referido: 76 milhões USD. Parcerias: Origem: Scatec (52,5%), EDM (25%), Norfund (22,5%); financiamento IFC e Fundo Africano de Infra-estruturas Emergentes; contrato de compra de energia de 25 anos. Adquirida pela Globeleq em Dezembro de 2023. Estado na fonte: Inaugurada em Agosto de 2019, a primeira de grandes dimensões no país; em operação em 2026.",
"Central Solar de Metoro (Cabo Delgado) — Solar. Capacidade: 41 MWp; cerca de 69 GWh /ano. Investimento referido: 56 milhões USD (MIREME); 47,3 milhões EUR (Lusa/ ALER). Parcerias: Neoen (75%) e EDM (25%); empréstimo da AFD de 40 milhões USD. Estado na fonte: Inaugurada em 1 de Abril de 2022.",
"Central Solar de Cuamba (Niassa) — Solar com baterias. Capacidade: 19 MWp (15 MWac) com baterias de 2 MW / 7 MWh. Investimento referido: 36 milhões USD. Parcerias: Globeleq, Source Energia e EDM; dívida de 19 milhões USD do EAIF / PIDG; contrato de compra de energia de 25 anos. Estado na fonte: Em operação e a injectar cerca de 15 MW na rede em 2026 (fecho financeiro em Dezembro de 2021).",
"Parque eólico de Namaacha — Eólica. Investimento estimado: ~260 milhões USD. Estado/calendário na fonte: Em desenvolvimento (120 MW); obras previstas para 2027, após fecho financeiro."
],
"indicators": [],
"risks": [
"Segurança: Instabilidade em Cabo Delgado já adiou projectos de GNL. Resposta a considerar: Acompanhar a situação operacional e exigir planos de segurança.",
"Macrofiscal: A consolidação fiscal é essencial para atrair o sector privado. Resposta a considerar: Parcerias com financiadores multilaterais e garantias.",
"Ambiental e social: Barragens e GNL exigem gestão de impactos e realojamento. Resposta a considerar: Estudos de impacto e planos de envolvimento comunitário.",
"Mercado: Maior concorrência global entre projectos de GNL e financiamento condicionado. Resposta a considerar: Diversificar entre gás, renováveis e redes."
],
"sources": [
"Instituto Nacional de Petróleo – Oportunidades de Investimento",
"Novimoz – Gás natural em Moçambique: exportação e renováveis aceleram debate",
"Efacont – Energia Moçambique: Projetos e Oportunidades de Negócio",
"Integrity Magazine – Moçambique poderá avançar com dois grandes projectos energéticos",
"EY Moçambique – Transição Energética em Moçambique",
"Boletim da República – Lei n.º 9/2026 (Lei do Conteúdo Local)",
"BCL – A Lei do Conteúdo Local está disponível para consulta pública",
"APIEX – Legislação de Investimentos em Moçambique",
"INP – Políticas e Quadro Legal",
"Gasoduto – Ministro da Energia sugere cadeias de valor do gás",
"O Económico – Oportunidades Industriais do Gás Natural em Moçambique",
"MMO – Ministro incentiva empresários moçambicanos a aproveitarem oportunidades do gás natural",
"Diário Económico – Grupo ETG quer produzir fertilizantes a partir do gás natural",
"Agroportal – Empresas de fertilizantes e energia interessadas em investir em Moçambique",
"RTP – Moçambique quer entrar na produção de hidrogénio e ser líder na África austral até 2030",
"Jornal Notícias – Estuda-se viabilidade do hidrogénio verde",
"Boletim da República – Decreto n.º 61/2023 (Regulamento de Biocombustíveis)",
"USAID SPEED – Avaliação da política de mistura obrigatória de biocombustíveis em Moçambique",
"Jornal Domingo – MIREME apresenta quadro regulatório de biocombustível",
"EDM – Inauguração da Central Solar de Mocuba",
"MIREME – O país dispõe de mais uma central solar (Metoro)",
"ALER – Presidente moçambicano inaugura maior central solar do país",
"Camaramen – Acordo de financiamento da Central Solar de Cuamba",
"Diário Económico – EDM: Centrais de Cuamba e Mocuba reduzem perdas de energia na rede nacional"
],
"source_links": [
{
"title": "Instituto Nacional de Petróleo – Oportunidades de Investimento",
"url": "https://www.inp.gov.mz/oportunidades-de-investimento/",
"source_paragraph": 90
},
{
"title": "Novimoz – Gás natural em Moçambique: exportação e renováveis aceleram debate",
"url": "https://novimz.com/2026/08/21/gas-natural-em-mocambique-exportacao-e-renovaveis-aceleram-debate/",
"source_paragraph": 91
},
{
"title": "Efacont – Energia Moçambique: Projetos e Oportunidades de Negócio",
"url": "https://www.efacont.pt/blog/setor-energia-mocambique/",
"source_paragraph": 92
},
{
"title": "Integrity Magazine – Moçambique poderá avançar com dois grandes projectos energéticos",
"url": "https://integritymagazine.co.mz/arquivos/45893",
"source_paragraph": 93
},
{
"title": "EY Moçambique – Transição Energética em Moçambique",
"url": "https://www.ey.com/pt_mz/insights/energy-resources/transicao-energetica-em-mocambique",
"source_paragraph": 94
},
{
"title": "Boletim da República – Lei n.º 9/2026 (Lei do Conteúdo Local)",
"url": "https://mireme.gov.mz/wp-content/uploads/2026/09/Lei-de-Conteudo-Local-BR_104_I_SERIE_3.o_SUPLEMENTO_2026.pdf",
"source_paragraph": 95
},
{
"title": "BCL – A Lei do Conteúdo Local está disponível para consulta pública",
"url": "https://bcl.org.mz/noticia/a-lei-do-conteudo-local-esta-disponivel-para-consulta-publica-no-website-do-bcl/",
"source_paragraph": 96
},
{
"title": "APIEX – Legislação de Investimentos em Moçambique",
"url": "https://pt.apiex.gov.mz/wp-content/uploads/2025/02/LEGISLACAO-DE-INVESTIMENTOS-EM-MOCAMBIQUE.pdf",
"source_paragraph": 97
},
{
"title": "INP – Políticas e Quadro Legal",
"url": "https://www.inp.gov.mz/politicas-e-quadro-legal/",
"source_paragraph": 98
},
{
"title": "Gasoduto – Ministro da Energia sugere cadeias de valor do gás",
"url": "https://gasoduto.co.mz/2026/07/16/ministro-da-energia-sugere-cadeias-de-valor-do-gas/",
"source_paragraph": 99
},
{
"title": "O Económico – Oportunidades Industriais do Gás Natural em Moçambique",
"url": "https://www.oeconomico.com/oportunidades-industriais-do-gas-natural-em-mocambique/",
"source_paragraph": 100
},
{
"title": "MMO – Ministro incentiva empresários moçambicanos a aproveitarem oportunidades do gás natural",
"url": "https://noticias.mmo.co.mz/2026/06/ministro-incentiva-empresarios-mocambicanos-a-aproveitarem-oportunidades-do-gas-natural.html",
"source_paragraph": 101
},
{
"title": "Diário Económico – Grupo ETG quer produzir fertilizantes a partir do gás natural",
"url": "https://www.diarioeconomico.co.mz/2026/10/23/oilgas/grupo-etg-quer-produzir-fertilizantes-a-partir-do-gas-natural-no-pais/",
"source_paragraph": 102
},
{
"title": "Agroportal – Empresas de fertilizantes e energia interessadas em investir em Moçambique",
"url": "https://www.agroportal.pt/empresas-na-areas-dos-fertilizantes-e-da-energia-interessados-em-investir-mais-em-mocambique/",
"source_paragraph": 103
},
{
"title": "RTP – Moçambique quer entrar na produção de hidrogénio e ser líder na África austral até 2030",
"url": "https://www.rtp.pt/noticias/economia/mocambique-quer-entrar-na-producao-de-hidrogenio-e-ser-lider-na-africa-austral-ate-2030_n1551096",
"source_paragraph": 104
},
{
"title": "Jornal Notícias – Estuda-se viabilidade do hidrogénio verde",
"url": "https://www.jornalnoticias.co.mz/destaque/estuda-se-viabilidade-do-hidrogenio-verde/",
"source_paragraph": 105
},
{
"title": "Boletim da República – Decreto n.º 61/2023 (Regulamento de Biocombustíveis)",
"url": "https://faolex.fao.org/docs/pdf/moz223152.pdf",
"source_paragraph": 106
},
{
"title": "USAID SPEED – Avaliação da política de mistura obrigatória de biocombustíveis em Moçambique",
"url": "https://repositorio.up.ac.mz/server/api/core/bitstreams/6a5d12fc-736e-4a7b-8ccc-84365279377d/content",
"source_paragraph": 107
},
{
"title": "Jornal Domingo – MIREME apresenta quadro regulatório de biocombustível",
"url": "https://www.jornaldomingo.co.mz/economia/mireme-apresenta-quadro-regulatorio-de-biocombustivel/",
"source_paragraph": 108
},
{
"title": "EDM – Inauguração da Central Solar de Mocuba",
"url": "https://portal.edm.co.mz/en/node/1294",
"source_paragraph": 109
},
{
"title": "MIREME – O país dispõe de mais uma central solar (Metoro)",
"url": "https://mireme.gov.mz/o-pais-dispoe-de-mais-uma-central-solar/",
"source_paragraph": 110
},
{
"title": "ALER – Presidente moçambicano inaugura maior central solar do país",
"url": "https://www.aler-energia.org/pt/comunicacao/noticias/presidente-mocambicano-inaugura-maior-central-solar-do-pais/",
"source_paragraph": 111
},
{
"title": "Camaramen – Acordo de financiamento da Central Solar de Cuamba",
"url": "https://camaramen.co.mz/665/concluido-acordo-para-o-financiamento-da-central-solar-de-cuamba-em-niassa/",
"source_paragraph": 112
},
{
"title": "Diário Económico – EDM: Centrais de Cuamba e Mocuba reduzem perdas de energia na rede nacional",
"url": "https://www.diarioeconomico.co.mz/2026/05/28/oilgas/energia/edm-centrais-de-cuamba-e-mocuba-reduzem-perdas-de-energia-na-rede-nacional/",
"source_paragraph": 113
}
],
"background": [
{
"title": "Financiamento referido nas fontes",
"paragraphs": [
"Instituições como o Banco Mundial, o BAD e a União Europeia mobilizam financiamento para redes, interligações regionais e renováveis.",
"O novo programa do Banco Mundial (cerca de 6 mil milhões de dólares), as linhas do BAD, a iniciativa Global Gateway da União Europeia e o apoio do Exim Bank dos EUA ao Mozambique LNG (4,7 mil milhões de dólares) sustentam o ciclo actual. Parcerias público-privadas e quadros regulatórios estáveis são factores apontados para atrair capital."
]
},
{
"title": "Enquadramento sectorial referido nas fontes",
"paragraphs": [
"O investimento privado rege-se pela Lei de Investimentos (Lei n.º 8/2023, de 9 de Junho), que estabelece garantias, direitos e deveres dos investidores e prevê incentivos fiscais e não fiscais. Esta lei não se aplica aos investimentos feitos ao abrigo de legislação específica, como a prospecção, pesquisa e produção de petróleo e gás, mas aplica-se, nas matérias não reguladas, ao processamento, comercialização e transporte de produtos petrolíferos. O Código dos Benefícios Fiscais (Lei n.º 4/2009) completa o regime geral.",
"As operações petrolíferas têm regime próprio: a Lei n.º 27/2014 (alterada pela Lei n.º 14/2017) define a tributação e os benefícios fiscais do sector. Em 3 de Junho de 2026 foi aprovada uma nova Lei do Petróleo (Lei n.º 8/2026), que revoga a Lei n.º 21/2014 e a Lei n.º 16/2022. Os investidores devem verificar o texto actualizado e os respectivos contratos de concessão.",
"A Lei n.º 9/2026, de 3 de Junho, estabelece o regime de aquisição de bens, contratação de serviços e emprego de mão-de-obra nacional nos projectos de petróleo e gás natural. Aplica-se aos titulares de direitos de operações petrolíferas, a terceiros que executem essas operações e às entidades que com eles contratam, que devem garantir, por via contratual, que os subcontratados cumprem as regras, sob pena de responsabilidade solidária.",
"Entre os princípios da lei estão a preservação do interesse nacional, a capacitação dos empresários moçambicanos, a contratação de mão-de-obra nacional com transferência de conhecimento, o incentivo a parcerias entre empresas nacionais e estrangeiras e a transparência nas aquisições. A lei prevê a criação de uma Autoridade de Conteúdo Local, e um regulamento deverá definir os planos de aquisição, a certificação e a monitoria. A sua aprovação pelo Conselho de Ministros era esperada em cerca de 90 dias após a publicação."
]
},
{
"title": "Âmbito dos dados solares",
"paragraphs": [
"Os dados de Metoro diferem entre fontes, sobretudo no valor do investimento, por isso devem ser confirmados com a EDM. Em Mocuba, parte da receita anual destina-se a programas sociais na comunidade, e a Cuamba é um dos primeiros projectos do país a integrar armazenamento em baterias, o que sugere oportunidades em sistemas de armazenamento.",
"Estado actual: segundo o director-executivo da Globeleq em Moçambique, as centrais de Mocuba e Cuamba estão em operação, foram adquiridas pela empresa em Dezembro de 2023 e injectam 48 MW na rede da EDM (33 MW em Mocuba e 15 MW em Cuamba), reduzindo as perdas de transmissão nas regiões centro e norte. Esta informação vem de uma declaração do promotor divulgada pela imprensa; a referência consultada não apresenta confirmação directa da EDM nem dados de 2026 sobre Metoro ."
]
}
],
"original_tables": [
[
[
"Preparado por",
"Osmane Nala"
],
[
"Data",
"11 de Novembro de 2026"
],
[
"Âmbito",
"Gás natural, hidroeléctrica, redes e energias renováveis"
]
],
[
[
"Projecto",
"Segmento",
"Investimento estimado",
"Estado / calendário"
],
[
"Mozambique LNG (TotalEnergies)",
"Gás natural",
"~20 mil milhões USD",
"Relançado em Jan. 2026; primeira produção em 2029"
],
[
"Rovuma LNG (ExxonMobil)",
"Gás natural",
"~24 mil milhões USD",
"Pré-decisão final de investimento"
],
[
"Coral Norte FLNG (Eni)",
"Gás natural",
"6 a 7 mil milhões USD",
"Decisão final tomada em 2025; capacidade a partir de 2028"
],
[
"Mphanda Nkuwa",
"Hidroeléctrica",
"~4,5 mil milhões EUR",
"1.500 MW; conclusão prevista para 2031"
],
[
"Interligação Moçambique-Zâmbia",
"Transmissão",
"n.d.",
"Em execução, com apoio do BAD"
],
[
"Central solar de Afungi",
"Solar",
"10 a 15 milhões USD",
"Concurso EPC lançado (7,1 MW)"
],
[
"Central Solar de Mocuba",
"Solar",
"76 milhões USD",
"Em operação; injecta cerca de 33 MW na rede (2026)"
],
[
"Central Solar de Metoro",
"Solar",
"56 milhões USD (MIREME)",
"Inaugurada em 2022; 41 MWp"
],
[
"Central Solar de Cuamba",
"Solar com baterias",
"36 milhões USD",
"Em operação; injecta cerca de 15 MW na rede (2026)"
],
[
"Parque eólico de Namaacha",
"Eólica",
"~260 milhões USD",
"Em desenvolvimento (120 MW); obras previstas para 2027, após fecho financeiro"
]
],
[
[
"Projecto",
"Capacidade",
"Investimento",
"Parceiros e financiamento",
"Estado"
],
[
"Central Solar de Mocuba (Zambézia)",
"40 MW instalados (cerca de 33 MW injectados na rede); cerca de 79 GWh /ano",
"76 milhões USD",
"Origem: Scatec (52,5%), EDM (25%), Norfund (22,5%); financiamento IFC e Fundo Africano de Infra-estruturas Emergentes; contrato de compra de energia de 25 anos. Adquirida pela Globeleq em Dezembro de 2023",
"Inaugurada em Agosto de 2019, a primeira de grandes dimensões no país; em operação em 2026"
],
[
"Central Solar de Metoro (Cabo Delgado)",
"41 MWp; cerca de 69 GWh /ano",
"56 milhões USD (MIREME); 47,3 milhões EUR (Lusa/ ALER)",
"Neoen (75%) e EDM (25%); empréstimo da AFD de 40 milhões USD",
"Inaugurada em 1 de Abril de 2022"
],
[
"Central Solar de Cuamba (Niassa)",
"19 MWp (15 MWac) com baterias de 2 MW / 7 MWh",
"36 milhões USD",
"Globeleq, Source Energia e EDM; dívida de 19 milhões USD do EAIF / PIDG; contrato de compra de energia de 25 anos",
"Em operação e a injectar cerca de 15 MW na rede em 2026 (fecho financeiro em Dezembro de 2021)"
]
],
[
[
"Elo da cadeia",
"Exemplos de oportunidades",
"Perfil de investidor"
],
[
"Upstream",
"Concursos de áreas, campos maduros (Buzi), parcerias com empresas locais",
"Operadores e parceiros locais"
],
[
"Construção e serviços",
"Engenharia, logística, serviços marítimos, telecomunicações, manutenção, formação",
"PME nacionais e parcerias internacionais"
],
[
"Midstream",
"Gasodutos Norte-Sul e Palma–Maputo, armazenamento de GPL, GNL de pequena escala",
"Investidores em infra-estruturas"
],
[
"Industrialização",
"Fertilizantes, petroquímica, geração eléctrica a gás",
"Grupos industriais e produtores independentes de energia"
],
[
"Mercado final",
"GNV, biocombustíveis, mobilidade eléctrica",
"Empresas de distribuição e mobilidade"
]
],
[
[
"Diploma",
"Matéria",
"Relevância para o investidor"
],
[
"Lei n.º 8/2023 (Lei de Investimentos)",
"Garantias, direitos, deveres e incentivos",
"Regime geral; não cobre a pesquisa e produção de petróleo e gás"
],
[
"Lei n.º 4/2009 (Código dos Benefícios Fiscais)",
"Benefícios fiscais",
"Base dos incentivos fiscais"
],
[
"Leis n.º 27/2014 e 14/2017",
"Tributação e benefícios das operações petrolíferas",
"Regime fiscal específico do sector"
],
[
"Lei n.º 8/2026 (Lei do Petróleo)",
"Atribuição de direitos petrolíferos",
"Substitui a Lei n.º 21/2014"
],
[
"Lei n.º 9/2026 (Conteúdo Local)",
"Bens, serviços e mão-de-obra nacionais",
"Obriga a priorizar fornecedores e trabalhadores moçambicanos"
]
],
[
[
"Risco",
"Descrição",
"Mitigação"
],
[
"Segurança",
"Instabilidade em Cabo Delgado já adiou projectos de GNL.",
"Acompanhar a situação operacional e exigir planos de segurança."
],
[
"Macrofiscal",
"A consolidação fiscal é essencial para atrair o sector privado.",
"Parcerias com financiadores multilaterais e garantias."
],
[
"Ambiental e social",
"Barragens e GNL exigem gestão de impactos e realojamento.",
"Estudos de impacto e planos de envolvimento comunitário."
],
[
"Mercado",
"Maior concorrência global entre projectos de GNL e financiamento condicionado.",
"Diversificar entre gás, renováveis e redes."
]
]
],
"original_paragraphs": [
"Oportunidades de investimento em energia",
"Panorama do sector energético em Moçambique",
"",
"Índice",
"Resumo executivo 3",
"Gás natural e GNL 3",
"Electricidade, hidroeléctricas e redes 3",
"Energias renováveis 4",
"Projectos solares em operação ou financiados 5",
"Outras oportunidades e cadeia de valor 6",
"Do gás exportado ao gás transformado 6",
"Oportunidades adicionais 7",
"Cadeia de valor e papel das empresas nacionais 7",
"Hidrogénio verde 8",
"Biocombustíveis 8",
"Outras fontes de energia 8",
"Financiamento 9",
"Incentivos fiscais, quadro legal e conteúdo local 9",
"Quadro legal geral 9",
"Regime específico do petróleo e gás 9",
"Lei do Conteúdo Local 9",
"Riscos e desafios 10",
"Recomendações 10",
"Conclusão 11",
"Fontes 11",
"Resumo executivo",
"Moçambique atravessa um ciclo de investimento energético particularmente relevante, assente no relançamento dos projectos de gás natural liquefeito (GNL) na bacia do Rovuma, na expansão da capacidade hidroeléctrica e na aposta crescente em energia solar e nas redes de transmissão.",
"O Mozambique LNG (TotalEnergies), com investimento estimado em cerca de 20 mil milhões de dólares, foi relançado em Janeiro de 2026, com primeira produção apontada para 2029.",
"A hidroeléctrica representa cerca de 70% da electricidade nacional, e a estratégia de transição energética prevê mais 2 a 4 GW hídricos e 2 GW solares até 2030.",
"Instituições como o Banco Mundial, o BAD e a União Europeia mobilizam financiamento para redes, interligações regionais e renováveis.",
"O investimento exige, contudo, atenção à segurança em Cabo Delgado, à estabilidade macrofiscal e à gestão dos impactos ambientais e sociais.",
"Principal conclusão",
"As maiores oportunidades concentram-se no gás natural (upstream, midstream e cadeia de fornecimento), na expansão de redes e interligações e em projectos solares e hídricos com apoio de financiadores multilaterais.",
"Gás natural e GNL",
"A bacia do Rovuma concentra as maiores oportunidades do país. O Mozambique LNG (Área 1) prevê uma capacidade anual de 12,9 milhões de toneladas; o Coral Norte FLNG (Eni) teve decisão final de investimento em 2025, com 3,6 milhões de toneladas anuais a partir de 2028; e o Rovuma LNG (ExxonMobil), estimado em cerca de 24 mil milhões de dólares, aproxima-se da decisão final de investimento.",
"Para investidores e empresas nacionais, as oportunidades estendem-se a engenharia, construção, logística, serviços marítimos, telecomunicações e manutenção industrial. No mercado interno, o Instituto Nacional de Petróleo identifica áreas como o gasoduto Norte-Sul, o campo maduro de Buzi, o GNL de pequena escala, o armazenamento de GPL e a base logística de Pemba.",
"Electricidade, hidroeléctricas e redes",
"A Hidroeléctrica de Cahora Bassa, com 2.075 MW instalados, mantém um papel central. Estão em preparação a barragem de Mphanda Nkuwa (1.500 MW no rio Zambeze, com conclusão prevista para 2031), a expansão de Cahora Bassa Norte (1.250 MW) e a linha de transmissão Maputo-Temane.",
"A modernização da rede da EDM e a interligação Moçambique-Zâmbia de 400 kV, apoiada pelo BAD, abrem espaço para fornecimento de subestações, transformadores, protecções eléctricas e sistemas SCADA, além de reforçar a exportação regional de energia.",
"Energias renováveis",
"Moçambique recebe cerca de 2.500 horas de sol por ano e dispõe de potencial eólico ao longo da costa. A estratégia nacional prevê 2 GW de energia solar até 2030, e já existem projectos como a central solar de 7,1 MW associada ao complexo de Afungi e um parque solar de 400 MW ligado a Cahora Bassa. Os sistemas fora da rede permitem ainda alargar o acesso à electricidade onde a rede é mais lenta ou dispendiosa de expandir.",
"Projectos energéticos em destaque",
"Projectos solares em operação ou financiados",
"Os projectos abaixo estão documentados em fontes oficiais (MIREME e EDM) ou em comunicados dos promotores. Mostram o modelo dominante: produtores independentes em parceria com a EDM, com contratos de compra de energia de longo prazo e financiamento de instituições de desenvolvimento.",
"Centrais solares concretas em Moçambique",
"Os dados de Metoro diferem entre fontes, sobretudo no valor do investimento, por isso devem ser confirmados com a EDM. Em Mocuba, parte da receita anual destina-se a programas sociais na comunidade, e a Cuamba é um dos primeiros projectos do país a integrar armazenamento em baterias, o que sugere oportunidades em sistemas de armazenamento.",
"Estado actual: segundo o director-executivo da Globeleq em Moçambique, as centrais de Mocuba e Cuamba estão em operação, foram adquiridas pela empresa em Dezembro de 2023 e injectam 48 MW na rede da EDM (33 MW em Mocuba e 15 MW em Cuamba), reduzindo as perdas de transmissão nas regiões centro e norte. Esta informação vem de uma declaração do promotor divulgada pela imprensa; não encontrei uma confirmação directa da EDM, nem dados de 2026 sobre Metoro .",
"Quanto à eólica, a Globeleq indicou em 2026 que desenvolve, com a EDM e a Source Energia, o parque eólico de Namaacha (Maputo), com 120 MW previstos e investimento global estimado em cerca de 260 milhões de dólares. O início das obras está previsto para 2027, depois do fecho financeiro, pelo que o projecto ainda não está em construção.",
"Outras oportunidades e cadeia de valor",
"Do gás exportado ao gás transformado",
"O Governo defende que o gás natural não deve ser visto apenas como produto de exportação, mas como matéria-prima para criar indústrias, empresas e emprego, com usos que vão da electricidade aos fertilizantes. O ministro dos Recursos Minerais e Energia identificou como desafios do sector privado o acesso a financiamento, a certificação e a tecnologia.",
"Um estudo do Programa de Partilha de Conhecimentos (KSP) propõe um corredor energético Palma–Maputo para integrar petroquímica, fertilizantes e geração eléctrica, e destaca o gás natural veicular (GNV) nos transportes.",
"Oportunidades adicionais",
"Fertilizantes: o grupo Export Trading Group (ETG) manifestou, em Outubro de 2026, interesse em produzir fertilizantes a partir do gás natural, com o objectivo de liderar a produção na África Subsariana.",
"Geração e transmissão de electricidade: a Azura Power, co-proprietária da central térmica de Ressano Garcia, declarou ambição de investir mais em geração, transmissão e infra-estruturas relacionadas, sem indicar projectos ou valores.",
"Gás natural veicular e biocombustíveis: o Governo considera GNV, conversão de gás em electricidade, mobilidade eléctrica e biocombustíveis respostas à volatilidade dos preços dos combustíveis.",
"Mercado interno: gasodutos e projectos de GNV orientados para o consumo doméstico abrem espaço ao sector privado.",
"Cadeia de valor e papel das empresas nacionais",
"Está prevista uma componente de formação para 15.734 moçambicanos (custo estimado em 126 milhões de dólares) e a capacitação de 355 pequenas e médias empresas na fase de construção e cerca de 1.500 na fase operacional. O ministro apelou a parcerias com entidades estrangeiras especializadas para transferência de conhecimento e tecnologia.",
"Elos da cadeia de valor do gás e oportunidades",
"Hidrogénio verde",
"A Estratégia de Transição Energética (ETS) prevê investimentos de cerca de 80 mil milhões de dólares até 2050 e fixa a meta de posicionar Moçambique entre os líderes na produção de hidrogénio na África austral até 2030. O Governo aponta a combinação de recursos hídricos, gás, solar e eólico como base para a produção, sobretudo de hidrogénio verde, e prevê que seja maioritariamente exportado. A ETS refere ainda a necessidade de instalações de produção, armazenamento e redes de transporte, e parcerias regionais.",
"O sector está numa fase inicial: o banco alemão KfW e a EDM assinaram, em Março de 2025, um acordo de 500 mil euros para estudos de mercado e de viabilidade técnica e económica do hidrogénio verde. Existem, por isso, oportunidades em estudos, consultoria, parcerias e infra-estruturas de apoio, mas ainda não encontrei projectos com decisão final de investimento.",
"Biocombustíveis",
"O Decreto n.º 61/2023 regula a produção, transporte, armazenagem, distribuição, comercialização e exportação de biocombustíveis e prevê que importadores e distribuidores de combustíveis líquidos adquiram biocombustível produzido no país para mistura. A produção acima de 1.000.000 de litros por ano é licenciada pelo ministro da Energia. Um estudo do programa USAID SPEED indica que as misturas B3 (biodiesel) e E10 (etanol) estavam previstas para iniciar em Agosto de 2024 e que as matérias-primas imediatamente disponíveis são a cana-de-açúcar (etanol) e o óleo de soja e de algodão (biodiesel).",
"O mesmo estudo lembra que a anterior tentativa de criar a indústria (2005–2013, centrada na jatrofa e na exportação) não teve sucesso, por falta de sementes de qualidade, apoio à cadeia de valor e mercado, e que há preocupações com terra, água e segurança alimentar. Em Junho de 2026, o MIREME promoveu o primeiro seminário nacional de biocombustíveis para identificar instrumentos de financiamento e operacionalizar a mistura obrigatória.",
"Outras fontes de energia",
"Hidroeléctrica: a ETS prevê 3,5 GW de nova capacidade hídrica entre 2024 e 2030, incluindo a conclusão de Mphanda Nkuwa (1.500 MW), um projecto que o Governo procurou apresentar a parceiros estratégicos.",
"Solar e eólica: a ETS prevê um programa de leilões de energia renovável para impulsionar estas fontes, bem como a expansão e modernização da rede para absorver a nova geração.",
"Parques industriais verdes: a ETS refere corredores e parques industriais abastecidos por energia limpa, uma oportunidade para indústria e infra-estruturas.",
"Financiamento",
"O novo programa do Banco Mundial (cerca de 6 mil milhões de dólares), as linhas do BAD, a iniciativa Global Gateway da União Europeia e o apoio do Exim Bank dos EUA ao Mozambique LNG (4,7 mil milhões de dólares) sustentam o ciclo actual. Parcerias público-privadas e quadros regulatórios estáveis são factores apontados para atrair capital.",
"Incentivos fiscais, quadro legal e conteúdo local",
"Quadro legal geral",
"O investimento privado rege-se pela Lei de Investimentos (Lei n.º 8/2023, de 9 de Junho), que estabelece garantias, direitos e deveres dos investidores e prevê incentivos fiscais e não fiscais. Esta lei não se aplica aos investimentos feitos ao abrigo de legislação específica, como a prospecção, pesquisa e produção de petróleo e gás, mas aplica-se, nas matérias não reguladas, ao processamento, comercialização e transporte de produtos petrolíferos. O Código dos Benefícios Fiscais (Lei n.º 4/2009) completa o regime geral.",
"Regime específico do petróleo e gás",
"As operações petrolíferas têm regime próprio: a Lei n.º 27/2014 (alterada pela Lei n.º 14/2017) define a tributação e os benefícios fiscais do sector. Em 3 de Junho de 2026 foi aprovada uma nova Lei do Petróleo (Lei n.º 8/2026), que revoga a Lei n.º 21/2014 e a Lei n.º 16/2022. Os investidores devem verificar o texto actualizado e os respectivos contratos de concessão.",
"Lei do Conteúdo Local",
"A Lei n.º 9/2026, de 3 de Junho, estabelece o regime de aquisição de bens, contratação de serviços e emprego de mão-de-obra nacional nos projectos de petróleo e gás natural. Aplica-se aos titulares de direitos de operações petrolíferas, a terceiros que executem essas operações e às entidades que com eles contratam, que devem garantir, por via contratual, que os subcontratados cumprem as regras, sob pena de responsabilidade solidária.",
"Entre os princípios da lei estão a preservação do interesse nacional, a capacitação dos empresários moçambicanos, a contratação de mão-de-obra nacional com transferência de conhecimento, o incentivo a parcerias entre empresas nacionais e estrangeiras e a transparência nas aquisições. A lei prevê a criação de uma Autoridade de Conteúdo Local, e um regulamento deverá definir os planos de aquisição, a certificação e a monitoria. A sua aprovação pelo Conselho de Ministros era esperada em cerca de 90 dias após a publicação.",
"Resumo do enquadramento para investidores",
"Riscos e desafios",
"Recomendações",
"Posicionar-se na cadeia de valor — Conteúdo local : registar-se junto dos grandes projectos de GNL e fornecer serviços de engenharia, logística e manutenção.",
"Explorar redes e interligações — Transmissão : acompanhar os concursos da EDM e da interligação Moçambique-Zâmbia para equipamentos e sistemas.",
"Apostar em solar e sistemas fora da rede — Diversificação : desenvolver projectos de menor escala com acesso a financiamento multilateral.",
"Gerir o risco desde o início — Segurança e conformidade : estruturar parcerias com empresas locais e prever cenários de atraso.",
"Conclusão",
"O sector energético moçambicano combina um ciclo de grandes projectos de gás com necessidades concretas de electricidade, redes e renováveis. Os investidores que conciliarem a escala dos megaprojectos com oportunidades de menor dimensão e uma gestão rigorosa do risco estarão melhor posicionados.",
"Fontes",
"Instituto Nacional de Petróleo – Oportunidades de Investimento",
"Novimoz – Gás natural em Moçambique: exportação e renováveis aceleram debate",
"Efacont – Energia Moçambique: Projetos e Oportunidades de Negócio",
"Integrity Magazine – Moçambique poderá avançar com dois grandes projectos energéticos",
"EY Moçambique – Transição Energética em Moçambique",
"Boletim da República – Lei n.º 9/2026 (Lei do Conteúdo Local)",
"BCL – A Lei do Conteúdo Local está disponível para consulta pública",
"APIEX – Legislação de Investimentos em Moçambique",
"INP – Políticas e Quadro Legal",
"Gasoduto – Ministro da Energia sugere cadeias de valor do gás",
"O Económico – Oportunidades Industriais do Gás Natural em Moçambique",
"MMO – Ministro incentiva empresários moçambicanos a aproveitarem oportunidades do gás natural",
"Diário Económico – Grupo ETG quer produzir fertilizantes a partir do gás natural",
"Agroportal – Empresas de fertilizantes e energia interessadas em investir em Moçambique",
"RTP – Moçambique quer entrar na produção de hidrogénio e ser líder na África austral até 2030",
"Jornal Notícias – Estuda-se viabilidade do hidrogénio verde",
"Boletim da República – Decreto n.º 61/2023 (Regulamento de Biocombustíveis)",
"USAID SPEED – Avaliação da política de mistura obrigatória de biocombustíveis em Moçambique",
"Jornal Domingo – MIREME apresenta quadro regulatório de biocombustível",
"EDM – Inauguração da Central Solar de Mocuba",
"MIREME – O país dispõe de mais uma central solar (Metoro)",
"ALER – Presidente moçambicano inaugura maior central solar do país",
"Camaramen – Acordo de financiamento da Central Solar de Cuamba",
"Diário Económico – EDM: Centrais de Cuamba e Mocuba reduzem perdas de energia na rede nacional"
],
"source_instructions_are_not_product_rules": true
}
File diff suppressed because it is too large Load Diff
+806 -9
View File
@@ -4,8 +4,18 @@
"fdel": {
"edition": "fdel-br43-20250305-tete20251020-v1",
"provenance": [
{ "url": "https://mpd.gov.mz/wp-content/uploads/2025/10/BR_43_I_SERIE_2025.pdf", "sha256": "0ea99c5f894760e3b715e377e87e9715fad9423e322e5753de1b9bb345c64753", "checked_on": "2026-11-02", "reference": "BR I Série n.º 43, 05/03/2025, pp. 157–160; Decreto n.º 4/2025 e Regulamento anexo" },
{ "url": "https://cmct.gov.mz/fundo-de-desenvolvimento-economico-local-fdel/", "sha256": "c69c2c3883766aa6a238ae44d8ee3fc99570adca2f3e647e50df59fde2786403", "checked_on": "2026-11-02", "reference": "Aviso municipal FDEL, prazo 20/10/2025" }
{
"url": "https://mpd.gov.mz/wp-content/uploads/2025/10/BR_43_I_SERIE_2025.pdf",
"sha256": "0ea99c5f894760e3b715e377e87e9715fad9423e322e5753de1b9bb345c64753",
"checked_on": "2026-11-02",
"reference": "BR I Série n.º 43, 05/03/2025, pp. 157–160; Decreto n.º 4/2025 e Regulamento anexo"
},
{
"url": "https://cmct.gov.mz/fundo-de-desenvolvimento-economico-local-fdel/",
"sha256": "c69c2c3883766aa6a238ae44d8ee3fc99570adca2f3e647e50df59fde2786403",
"checked_on": "2026-11-02",
"reference": "Aviso municipal FDEL, prazo 20/10/2025"
}
],
"draft": {
"title": "Fundo de Desenvolvimento Económico Local — FDEL",
@@ -25,8 +35,22 @@
"sector_ids": [],
"resource_ids": [],
"criteria": [
{ "code": "nationality-individual", "question": "Tem nacionalidade moçambicana?", "applies_to": "individual", "expected": "yes", "source_reference": "Decreto n.º 4/2025, art. 3", "explanation": "Condição declarada; não substitui a apreciação competente." },
{ "code": "economic-viability", "question": "O projecto apresenta elementos de viabilidade económica?", "applies_to": "all", "expected": "yes", "source_reference": "Decreto n.º 4/2025, art. 3; Regulamento, art. 13", "explanation": "A avaliação do projecto cabe às entidades competentes." }
{
"code": "nationality-individual",
"question": "Tem nacionalidade moçambicana?",
"applies_to": "individual",
"expected": "yes",
"source_reference": "Decreto n.º 4/2025, art. 3",
"explanation": "Condição declarada; não substitui a apreciação competente."
},
{
"code": "economic-viability",
"question": "O projecto apresenta elementos de viabilidade económica?",
"applies_to": "all",
"expected": "yes",
"source_reference": "Decreto n.º 4/2025, art. 3; Regulamento, art. 13",
"explanation": "A avaliação do projecto cabe às entidades competentes."
}
],
"windows": [
{
@@ -42,15 +66,788 @@
"checked_on": "2026-11-02",
"application_url": "https://cmct.gov.mz/anexo-5-plano-de-negocio/",
"criteria": [
{ "code": "tete-nuit", "question": "Dispõe de NUIT?", "applies_to": "all", "expected": "yes", "source_reference": "Aviso municipal, Requisitos", "explanation": "Documentação indicada para esta chamada histórica." },
{ "code": "tete-identity", "question": "Dispõe de cópia do BI?", "applies_to": "individual", "expected": "yes", "source_reference": "Aviso municipal, Candidatos individuais", "explanation": "Aplica-se a pessoas singulares nesta chamada." },
{ "code": "tete-registration", "question": "A entidade dispõe de comprovativo de registo legal?", "applies_to": "entity", "expected": "yes", "source_reference": "Aviso municipal, Entidades", "explanation": "Aplica-se às entidades indicadas no aviso." },
{ "code": "tete-idoneity", "question": "Dispõe de atestado de idoneidade emitido pela sede do bairro?", "applies_to": "individual", "expected": "yes", "source_reference": "Aviso municipal, Candidatos individuais", "explanation": "Documento indicado no aviso." },
{ "code": "tete-national-entity", "question": "A entidade é constituída por cidadãos nacionais?", "applies_to": "entity", "expected": "yes", "source_reference": "Aviso municipal, Entidades", "explanation": "Condição específica desta chamada." }
{
"code": "tete-nuit",
"question": "Dispõe de NUIT?",
"applies_to": "all",
"expected": "yes",
"source_reference": "Aviso municipal, Requisitos",
"explanation": "Documentação indicada para esta chamada histórica."
},
{
"code": "tete-identity",
"question": "Dispõe de cópia do BI?",
"applies_to": "individual",
"expected": "yes",
"source_reference": "Aviso municipal, Candidatos individuais",
"explanation": "Aplica-se a pessoas singulares nesta chamada."
},
{
"code": "tete-registration",
"question": "A entidade dispõe de comprovativo de registo legal?",
"applies_to": "entity",
"expected": "yes",
"source_reference": "Aviso municipal, Entidades",
"explanation": "Aplica-se às entidades indicadas no aviso."
},
{
"code": "tete-idoneity",
"question": "Dispõe de atestado de idoneidade emitido pela sede do bairro?",
"applies_to": "individual",
"expected": "yes",
"source_reference": "Aviso municipal, Candidatos individuais",
"explanation": "Documento indicado no aviso."
},
{
"code": "tete-national-entity",
"question": "A entidade é constituída por cidadãos nacionais?",
"applies_to": "entity",
"expected": "yes",
"source_reference": "Aviso municipal, Entidades",
"explanation": "Condição específica desta chamada."
}
]
}
]
}
},
"bci": {
"edition": "tempo-benchmark-20261004-directory-20261110-v1",
"provenance": [
{
"reference": "benchmark de melhorias investeMoz.docx; quadro comparativo 04/10/2026",
"sha256": "065203aacfae766c62cf9c2c272ebacc61ff23bd4ff9e91535939fede87f7617",
"checked_on": "2026-11-10"
},
{
"reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; BCI Exclusivo PME",
"url": "https://www.bci.co.mz/bci-exclusivo-pme/",
"checked_on": "2026-11-10"
}
],
"draft": {
"title": "BCI",
"kind": "institution",
"summary": "Conta corrente caucionada, empréstimo de médio/longo prazo, garantias e comércio externo. Público/finalidade: ENI, micro, pequenas e médias empresas.. Referência informativa, não concessão de financiamento.",
"manager_name": "BCI",
"source_url": "https://www.bci.co.mz/bci-exclusivo-pme/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; BCI Exclusivo PME",
"source_checked_on": "2026-11-10",
"source_status": "identified",
"coverage": "national",
"coverage_text": "Nacional; atendimento conforme rede e canais do banco.",
"territory_ids": [],
"sectors_scope": "unknown",
"sectors_text": "Critérios sectoriais por conferir no produto/programa; nenhuma elegibilidade automática.",
"sector_ids": [],
"resource_ids": [],
"criteria": [],
"windows": [],
"funding_needs": [
"working_capital",
"investment",
"guarantee"
],
"institution_key": "bci",
"institution_nature": "bank",
"contacts": [
{
"source_url": "https://www.bci.co.mz/bci-exclusivo-pme/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; BCI Exclusivo PME",
"checked_on": "2026-11-10",
"kind": "website",
"value": "https://www.bci.co.mz/bci-exclusivo-pme/"
}
],
"locations": [],
"products": [
{
"source_url": "https://www.bci.co.mz/bci-exclusivo-pme/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; BCI Exclusivo PME",
"checked_on": "2026-11-10",
"code": "bci-oferta",
"title": "BCI — Soluções PME",
"instrument": "Conta corrente caucionada, empréstimo de médio/longo prazo, garantias e comércio externo.",
"needs": [
"working_capital",
"investment",
"guarantee"
],
"coverage": "national",
"coverage_text": "Nacional; atendimento conforme rede e canais do banco.",
"territory_ids": [],
"conditions": "ENI, micro, pequenas e médias empresas. Oferta permanente publicada. A antiga “Linha BCI Negócios PME” terminou em 31 de Dezembro de 2018 e fica excluída das oportunidades activas.",
"amounts": "Variável segundo necessidade, produto e análise.",
"availability": "documented",
"contact_url": "https://www.bci.co.mz/bci-exclusivo-pme/"
}
]
}
},
"millennium-bim": {
"edition": "tempo-benchmark-20261004-directory-20261110-v1",
"provenance": [
{
"reference": "benchmark de melhorias investeMoz.docx; quadro comparativo 04/10/2026",
"sha256": "065203aacfae766c62cf9c2c272ebacc61ff23bd4ff9e91535939fede87f7617",
"checked_on": "2026-11-10"
},
{
"reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Financiamento para empresas",
"url": "https://www.millenniumbim.co.mz/pt/empresas/produtos/financiamento",
"checked_on": "2026-11-10"
}
],
"draft": {
"title": "Millennium bim",
"kind": "institution",
"summary": "Conta empréstimo, conta corrente caucionada, factoring, confirming , leasing e garantias. Público/finalidade: Empresas e ENI elegíveis.. Referência informativa, não concessão de financiamento.",
"manager_name": "Millennium bim",
"source_url": "https://www.millenniumbim.co.mz/pt/empresas/produtos/financiamento",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Financiamento para empresas",
"source_checked_on": "2026-11-10",
"source_status": "identified",
"coverage": "national",
"coverage_text": "Nacional; balcões e canais do banco.",
"territory_ids": [],
"sectors_scope": "unknown",
"sectors_text": "Critérios sectoriais por conferir no produto/programa; nenhuma elegibilidade automática.",
"sector_ids": [],
"resource_ids": [],
"criteria": [],
"windows": [],
"funding_needs": [
"working_capital",
"investment",
"guarantee"
],
"institution_key": "millennium-bim",
"institution_nature": "bank",
"contacts": [
{
"source_url": "https://www.millenniumbim.co.mz/pt/empresas/produtos/financiamento",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Financiamento para empresas",
"checked_on": "2026-11-10",
"kind": "website",
"value": "https://www.millenniumbim.co.mz/pt/empresas/produtos/financiamento"
}
],
"locations": [],
"products": [
{
"source_url": "https://www.millenniumbim.co.mz/pt/empresas/produtos/financiamento",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Financiamento para empresas",
"checked_on": "2026-11-10",
"code": "millennium-bim-oferta",
"title": "Millennium bim — Financiamento empresarial",
"instrument": "Conta empréstimo, conta corrente caucionada, factoring, confirming , leasing e garantias.",
"needs": [
"working_capital",
"investment",
"guarantee"
],
"coverage": "national",
"coverage_text": "Nacional; balcões e canais do banco.",
"territory_ids": [],
"conditions": "Empresas e ENI elegíveis. Oferta permanente publicada. Condições finais dependem da análise de risco e contrato.",
"amounts": "Conta empréstimo: investimento entre 3 e 180 meses na ficha publicada; restantes termos após análise.",
"availability": "documented",
"contact_url": "https://www.millenniumbim.co.mz/pt/empresas/produtos/financiamento"
}
]
}
},
"absa": {
"edition": "tempo-benchmark-20261004-directory-20261110-v1",
"provenance": [
{
"reference": "benchmark de melhorias investeMoz.docx; quadro comparativo 04/10/2026",
"sha256": "065203aacfae766c62cf9c2c272ebacc61ff23bd4ff9e91535939fede87f7617",
"checked_on": "2026-11-10"
},
{
"reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Empréstimos comerciais",
"url": "https://www.absa.co.mz/pt/banca-de-negocios/borrow/",
"checked_on": "2026-11-10"
}
],
"draft": {
"title": "Absa",
"kind": "institution",
"summary": "CAPEX , activos, importação, descoberto e leasing. Público/finalidade: PME e empresas com capacidade de endividamento.. Referência informativa, não concessão de financiamento.",
"manager_name": "Absa",
"source_url": "https://www.absa.co.mz/pt/banca-de-negocios/borrow/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Empréstimos comerciais",
"source_checked_on": "2026-11-10",
"source_status": "identified",
"coverage": "national",
"coverage_text": "Nacional; localização da empresa é recolhida no pedido.",
"territory_ids": [],
"sectors_scope": "unknown",
"sectors_text": "Critérios sectoriais por conferir no produto/programa; nenhuma elegibilidade automática.",
"sector_ids": [],
"resource_ids": [],
"criteria": [],
"windows": [],
"funding_needs": [
"working_capital",
"investment"
],
"institution_key": "absa",
"institution_nature": "bank",
"contacts": [
{
"source_url": "https://www.absa.co.mz/pt/banca-de-negocios/borrow/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Empréstimos comerciais",
"checked_on": "2026-11-10",
"kind": "website",
"value": "https://www.absa.co.mz/pt/banca-de-negocios/borrow/"
},
{
"source_url": "https://www.absa.co.mz/pt/banca-de-negocios/borrow/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Empréstimos comerciais",
"checked_on": "2026-11-10",
"kind": "phone",
"value": "+258 21 344 400"
},
{
"source_url": "https://www.absa.co.mz/pt/banca-de-negocios/borrow/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Empréstimos comerciais",
"checked_on": "2026-11-10",
"kind": "email",
"value": "linhacliente@absa.africa"
}
],
"locations": [],
"products": [
{
"source_url": "https://www.absa.co.mz/pt/banca-de-negocios/borrow/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Empréstimos comerciais",
"checked_on": "2026-11-10",
"code": "absa-oferta",
"title": "Absa — Empréstimos PME e activos",
"instrument": "CAPEX , activos, importação, descoberto e leasing.",
"needs": [
"working_capital",
"investment"
],
"coverage": "national",
"coverage_text": "Nacional; localização da empresa é recolhida no pedido.",
"territory_ids": [],
"conditions": "PME e empresas com capacidade de endividamento. Oferta permanente publicada. Critérios, documentos, garantias e ausência de registo desabonatório aplicam-se conforme produto.",
"amounts": "Leasing com prazo publicado até 5 anos; demais condições conforme análise.",
"availability": "documented",
"contact_url": "https://www.absa.co.mz/pt/banca-de-negocios/borrow/"
}
]
}
},
"standard-bank": {
"edition": "tempo-benchmark-20261004-directory-20261110-v1",
"provenance": [
{
"reference": "benchmark de melhorias investeMoz.docx; quadro comparativo 04/10/2026",
"sha256": "065203aacfae766c62cf9c2c272ebacc61ff23bd4ff9e91535939fede87f7617",
"checked_on": "2026-11-10"
},
{
"reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Conta Empréstimo",
"url": "https://www.standardbank.co.mz/Produtos/Empresas/Conta-Emprestimo",
"checked_on": "2026-11-10"
}
],
"draft": {
"title": "Standard Bank",
"kind": "institution",
"summary": "Crédito de médio/longo prazo para bens de capital e CAPEX . Público/finalidade: Empresas com capacidade de endividamento.. Referência informativa, não concessão de financiamento.",
"manager_name": "Standard Bank",
"source_url": "https://www.standardbank.co.mz/Produtos/Empresas/Conta-Emprestimo",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Conta Empréstimo",
"source_checked_on": "2026-11-10",
"source_status": "identified",
"coverage": "national",
"coverage_text": "Nacional; balcões e canais empresariais.",
"territory_ids": [],
"sectors_scope": "unknown",
"sectors_text": "Critérios sectoriais por conferir no produto/programa; nenhuma elegibilidade automática.",
"sector_ids": [],
"resource_ids": [],
"criteria": [],
"windows": [],
"funding_needs": [
"investment"
],
"institution_key": "standard-bank",
"institution_nature": "bank",
"contacts": [
{
"source_url": "https://www.standardbank.co.mz/Produtos/Empresas/Conta-Emprestimo",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Conta Empréstimo",
"checked_on": "2026-11-10",
"kind": "website",
"value": "https://www.standardbank.co.mz/Produtos/Empresas/Conta-Emprestimo"
},
{
"source_url": "https://www.standardbank.co.mz/Produtos/Empresas/Conta-Emprestimo",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Conta Empréstimo",
"checked_on": "2026-11-10",
"kind": "phone",
"value": "+258 21 355700"
}
],
"locations": [],
"products": [
{
"source_url": "https://www.standardbank.co.mz/Produtos/Empresas/Conta-Emprestimo",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Conta Empréstimo",
"checked_on": "2026-11-10",
"code": "standard-bank-oferta",
"title": "Standard Bank — Conta Empréstimo Empresas",
"instrument": "Crédito de médio/longo prazo para bens de capital e CAPEX .",
"needs": [
"investment"
],
"coverage": "national",
"coverage_text": "Nacional; balcões e canais empresariais.",
"territory_ids": [],
"conditions": "Empresas com capacidade de endividamento. Produto activo e publicado. Sujeito a análise e garantias.",
"amounts": "Até 7 anos; limite definido segundo capacidade e investimento; garantias reais.",
"availability": "documented",
"contact_url": "https://www.standardbank.co.mz/Produtos/Empresas/Conta-Emprestimo"
}
]
}
},
"moza": {
"edition": "tempo-benchmark-20261004-directory-20261110-v1",
"provenance": [
{
"reference": "benchmark de melhorias investeMoz.docx; quadro comparativo 04/10/2026",
"sha256": "065203aacfae766c62cf9c2c272ebacc61ff23bd4ff9e91535939fede87f7617",
"checked_on": "2026-11-10"
},
{
"reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Financiamento empresarial",
"url": "https://www.mozabanco.co.mz/pt/empresas/investimento/financiamento/",
"checked_on": "2026-11-10"
}
],
"draft": {
"title": "Moza Banco",
"kind": "institution",
"summary": "Tesouraria, investimento, construção e equipamento. Público/finalidade: Pequenas e médias empresas.. Referência informativa, não concessão de financiamento.",
"manager_name": "Moza Banco",
"source_url": "https://www.mozabanco.co.mz/pt/empresas/investimento/financiamento/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Financiamento empresarial",
"source_checked_on": "2026-11-10",
"source_status": "identified",
"coverage": "national",
"coverage_text": "Nacional; sem restrição territorial indicada no produto geral.",
"territory_ids": [],
"sectors_scope": "unknown",
"sectors_text": "Critérios sectoriais por conferir no produto/programa; nenhuma elegibilidade automática.",
"sector_ids": [],
"resource_ids": [],
"criteria": [],
"windows": [],
"funding_needs": [
"working_capital",
"investment"
],
"institution_key": "moza",
"institution_nature": "bank",
"contacts": [
{
"source_url": "https://www.mozabanco.co.mz/pt/empresas/investimento/financiamento/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Financiamento empresarial",
"checked_on": "2026-11-10",
"kind": "website",
"value": "https://www.mozabanco.co.mz/pt/empresas/investimento/financiamento/"
}
],
"locations": [],
"products": [
{
"source_url": "https://www.mozabanco.co.mz/pt/empresas/investimento/financiamento/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Financiamento empresarial",
"checked_on": "2026-11-10",
"code": "moza-oferta",
"title": "Moza Banco — Crédito PME",
"instrument": "Tesouraria, investimento, construção e equipamento.",
"needs": [
"working_capital",
"investment"
],
"coverage": "national",
"coverage_text": "Nacional; sem restrição territorial indicada no produto geral.",
"territory_ids": [],
"conditions": "Pequenas e médias empresas. Produto apresentado no portal. A ficha contém condições que devem ser reconfirmadas antes da publicação, incluindo montante, comissão e garantias.",
"amounts": "Ficha publicada: 500.000–30.000.000 MT; 6–60 meses; Prime + spread.",
"availability": "unknown",
"contact_url": "https://www.mozabanco.co.mz/pt/empresas/investimento/financiamento/"
}
]
}
},
"bni": {
"edition": "tempo-benchmark-20261004-directory-20261110-v1",
"provenance": [
{
"reference": "benchmark de melhorias investeMoz.docx; quadro comparativo 04/10/2026",
"sha256": "065203aacfae766c62cf9c2c272ebacc61ff23bd4ff9e91535939fede87f7617",
"checked_on": "2026-11-10"
},
{
"reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Banca de Desenvolvimento",
"url": "https://www.bni.co.mz/banca-de-desenvolvimento/",
"checked_on": "2026-11-10"
},
{
"reference": "BNI Capital: veículo do BNI, registado como produto sem duplicar a instituição",
"url": "https://www.bni.co.mz/banca-de-investimento/capital-de-risco/",
"checked_on": "2026-11-10"
}
],
"draft": {
"title": "BNI",
"kind": "institution",
"summary": "Crédito para sectores produtivos, infra-estruturas e projectos com impacto. Público/finalidade: Empresas e projectos de desenvolvimento.. Referência informativa, não concessão de financiamento.",
"manager_name": "BNI",
"source_url": "https://www.bni.co.mz/banca-de-desenvolvimento/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Banca de Desenvolvimento",
"source_checked_on": "2026-11-10",
"source_status": "identified",
"coverage": "national",
"coverage_text": "Nacional ou conforme fundo/linha específica.",
"territory_ids": [],
"sectors_scope": "unknown",
"sectors_text": "Critérios sectoriais por conferir no produto/programa; nenhuma elegibilidade automática.",
"sector_ids": [],
"resource_ids": [],
"criteria": [],
"windows": [],
"funding_needs": [
"investment"
],
"institution_key": "bni",
"institution_nature": "bank",
"contacts": [
{
"source_url": "https://www.bni.co.mz/banca-de-desenvolvimento/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Banca de Desenvolvimento",
"checked_on": "2026-11-10",
"kind": "website",
"value": "https://www.bni.co.mz/banca-de-desenvolvimento/"
},
{
"source_url": "https://www.bni.co.mz/contactos/",
"source_reference": "BNI — Contactos, endereço e telefone",
"checked_on": "2026-11-10",
"kind": "phone",
"value": "+258 21 498 581"
}
],
"locations": [
{
"source_url": "https://www.bni.co.mz/contactos/",
"source_reference": "BNI — Contactos, endereço e telefone",
"checked_on": "2026-11-10",
"role": "contact_point",
"label": "BNI — endereço institucional de Maputo",
"address": "Av. Julius Nyerere, 3504, Bloco A2, 4668 Maputo, Moçambique",
"territory_id": null
}
],
"products": [
{
"source_url": "https://www.bni.co.mz/banca-de-desenvolvimento/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Banca de Desenvolvimento",
"checked_on": "2026-11-10",
"code": "bni-oferta",
"title": "BNI — Crédito de desenvolvimento",
"instrument": "Crédito para sectores produtivos, infra-estruturas e projectos com impacto.",
"needs": [
"investment"
],
"coverage": "national",
"coverage_text": "Nacional ou conforme fundo/linha específica.",
"territory_ids": [],
"conditions": "Empresas e projectos de desenvolvimento. Serviço institucional publicado. Cada linha deve ter o seu próprio estado e regulamento.",
"amounts": "Definido por operação; o simulador oficial é apenas indicativo.",
"availability": "documented",
"contact_url": "https://www.bni.co.mz/banca-de-desenvolvimento/"
},
{
"source_url": "https://www.bni.co.mz/banca-de-investimento/capital-de-risco/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Capital de Risco",
"checked_on": "2026-11-10",
"code": "bni-capital-oferta",
"title": "BNI Capital",
"instrument": "Participação temporária e geralmente minoritária no capital; investimento em fundos.",
"needs": [
"equity"
],
"coverage": "national",
"coverage_text": "Moçambique, conforme tese e avaliação de investimento.",
"territory_ids": [],
"conditions": "Empresas não cotadas com crescimento, impacto, gestão profissional e insuficiência de capital. Serviço institucional publicado. Manifestação e processo dependem de avaliação do BNI .",
"amounts": "Negociado por operação; não é crédito.",
"availability": "documented",
"contact_url": "https://www.bni.co.mz/banca-de-investimento/capital-de-risco/"
}
]
},
"location_territory_names": {
"0": "Maputo Cidade"
}
},
"gapi": {
"edition": "tempo-benchmark-20261004-directory-20261110-v1",
"provenance": [
{
"reference": "benchmark de melhorias investeMoz.docx; quadro comparativo 04/10/2026",
"sha256": "065203aacfae766c62cf9c2c272ebacc61ff23bd4ff9e91535939fede87f7617",
"checked_on": "2026-11-10"
},
{
"reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Portal Gapi",
"url": "https://www.gapi.co.mz/",
"checked_on": "2026-10-04"
}
],
"draft": {
"title": "Gapi",
"kind": "institution",
"summary": "Crédito, garantias, capacitação e apoio empresarial. Público/finalidade: MPME , empresas rurais, agronegócio, jovens e mulheres, conforme programa.. Referência informativa, não concessão de financiamento.",
"manager_name": "Gapi",
"source_url": "https://www.gapi.co.mz/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Portal Gapi",
"source_checked_on": "2026-10-04",
"source_status": "identified",
"coverage": "unknown",
"coverage_text": "Presença nacional, com programas de cobertura territorial específica.",
"territory_ids": [],
"sectors_scope": "unknown",
"sectors_text": "Critérios sectoriais por conferir no produto/programa; nenhuma elegibilidade automática.",
"sector_ids": [],
"resource_ids": [],
"criteria": [],
"windows": [],
"funding_needs": [
"working_capital",
"investment",
"guarantee",
"technical_assistance"
],
"institution_key": "gapi",
"institution_nature": "other",
"contacts": [
{
"source_url": "https://www.gapi.co.mz/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Portal Gapi",
"checked_on": "2026-10-04",
"kind": "website",
"value": "https://www.gapi.co.mz/"
}
],
"locations": [],
"products": [
{
"source_url": "https://www.gapi.co.mz/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Portal Gapi",
"checked_on": "2026-10-04",
"code": "gapi-oferta",
"title": "Gapi — Financiamento e investimento de impacto",
"instrument": "Crédito, garantias, capacitação e apoio empresarial.",
"needs": [
"working_capital",
"investment",
"guarantee",
"technical_assistance"
],
"coverage": "unknown",
"coverage_text": "Presença nacional, com programas de cobertura territorial específica.",
"territory_ids": [],
"conditions": "MPME , empresas rurais, agronegócio, jovens e mulheres, conforme programa. Actividade de financiamento publicada. A Gapi declara não usar intermediários para conceder financiamento.",
"amounts": "Variável por linha; avaliação própria.",
"availability": "unknown",
"contact_url": "https://www.gapi.co.mz/"
}
]
}
},
"fecop": {
"edition": "tempo-benchmark-20261004-directory-20261110-v1",
"provenance": [
{
"reference": "benchmark de melhorias investeMoz.docx; quadro comparativo 04/10/2026",
"sha256": "065203aacfae766c62cf9c2c272ebacc61ff23bd4ff9e91535939fede87f7617",
"checked_on": "2026-11-10"
},
{
"reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Comunicado oficial Millennium bim",
"url": "https://www.millenniumbim.co.mz/media/gusl5uoe/20260624-comunicado-de-imprensa_millennium-bim-reforca-solucoes-de-financiamento-para-as-pme-atraves-do-fecop.pdf",
"checked_on": "2026-11-10"
}
],
"draft": {
"title": "FECOP — crédito por bancos aderentes",
"kind": "fund",
"summary": "Partilha de risco, crédito e assistência técnica. Público/finalidade: MPME e cooperativas de produtores elegíveis.. Referência informativa, não concessão de financiamento. Condições e montantes no quadro de 04/10/2026: Dotação global anunciada de cerca de €17 milhões; condições por banco e operação. Operacionalização confirmada em Junho de 2026. O Millennium bim mantém comunicado oficial; critérios e canal exacto devem ser confirmados em cada banco.",
"manager_name": "Entidades do programa identificadas na fonte",
"source_url": "https://www.millenniumbim.co.mz/media/gusl5uoe/20260624-comunicado-de-imprensa_millennium-bim-reforca-solucoes-de-financiamento-para-as-pme-atraves-do-fecop.pdf",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Comunicado oficial Millennium bim",
"source_checked_on": "2026-11-10",
"source_status": "identified",
"coverage": "national",
"coverage_text": "Moçambique; operacionalizado por Millennium bim , BCI e Vista Bank .",
"territory_ids": [],
"sectors_scope": "unknown",
"sectors_text": "Critérios sectoriais por conferir no produto/programa; nenhuma elegibilidade automática.",
"sector_ids": [],
"resource_ids": [],
"criteria": [],
"windows": [],
"funding_needs": [
"investment",
"working_capital",
"technical_assistance"
]
}
},
"semeia": {
"edition": "tempo-benchmark-20261004-directory-20261110-v1",
"provenance": [
{
"reference": "benchmark de melhorias investeMoz.docx; quadro comparativo 04/10/2026",
"sha256": "065203aacfae766c62cf9c2c272ebacc61ff23bd4ff9e91535939fede87f7617",
"checked_on": "2026-11-10"
},
{
"reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Semeia Negócios",
"url": "https://semeia.gapi.co.mz/",
"checked_on": "2026-11-10"
}
],
"draft": {
"title": "Semeia Negócios — FECOP",
"kind": "programme",
"summary": "Subvenção em espécie, formalização, capacitação e equipamento produtivo. Público/finalidade: Startups , micro e pequenas empresas; foco em jovens e mulheres.. Referência informativa, não concessão de financiamento. Condições e montantes no quadro de 04/10/2026: Até 450.000 MT; até 75% do investimento; subvenção sem juro. Candidaturas encerradas. O portal permite consultar candidaturas já submetidas.",
"manager_name": "Gapi / FECOP",
"source_url": "https://semeia.gapi.co.mz/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Semeia Negócios",
"source_checked_on": "2026-11-10",
"source_status": "identified",
"coverage": "national",
"coverage_text": "Moçambique; província e distrito constam da candidatura.",
"territory_ids": [],
"sectors_scope": "unknown",
"sectors_text": "Critérios sectoriais por conferir no produto/programa; nenhuma elegibilidade automática.",
"sector_ids": [],
"resource_ids": [],
"criteria": [],
"windows": [
{
"code": "quadro-20261004",
"title": "Chamada indicada no quadro fornecido — encerrada",
"mode": "closed",
"coverage": "programme",
"territory_ids": [],
"source_url": "https://semeia.gapi.co.mz/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Semeia Negócios",
"checked_on": "2026-11-10",
"application_url": "https://semeia.gapi.co.mz/",
"criteria": []
}
],
"funding_needs": [
"investment",
"technical_assistance"
]
}
},
"fcid-viii": {
"edition": "tempo-benchmark-20261004-directory-20261110-v1",
"provenance": [
{
"reference": "benchmark de melhorias investeMoz.docx; quadro comparativo 04/10/2026",
"sha256": "065203aacfae766c62cf9c2c272ebacc61ff23bd4ff9e91535939fede87f7617",
"checked_on": "2026-11-10"
},
{
"reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; VIII Edição FCID",
"url": "https://fundocatalitico.gov.mz/viii-edicao-fcid/",
"checked_on": "2026-11-10"
}
],
"draft": {
"title": "FCID VIII — Jobs Now",
"kind": "fund",
"summary": "Subvenção comparticipada para expansão produtiva, cadeias de valor e emprego. Público/finalidade: Pequenas e médias empresas e grandes empresas âncora formalizadas.. Referência informativa, não concessão de financiamento. Condições e montantes no quadro de 04/10/2026: Pequenas: USD 50.000–150.000; médias: USD 151.000–250.000; âncora: USD 251.000–500.000. Activo na data da verificação, com prazo iminente. O portal oficial mostrava candidaturas abertas e contagem decrescente; exige validação diária até ao encerramento.",
"manager_name": "Entidades do programa identificadas na fonte",
"source_url": "https://fundocatalitico.gov.mz/viii-edicao-fcid/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; VIII Edição FCID",
"source_checked_on": "2026-11-10",
"source_status": "identified",
"coverage": "territorial",
"coverage_text": "Províncias de Maputo, Gaza, Inhambane, Manica, Sofala e Zambézia. Não pressupõe cobertura nacional. Disponibilidade actual da janela por confirmar; a página indica candidaturas sem prazo final inequívoco.",
"territory_ids": [],
"sectors_scope": "unknown",
"sectors_text": "Critérios sectoriais por conferir no produto/programa; nenhuma elegibilidade automática.",
"sector_ids": [],
"resource_ids": [],
"criteria": [],
"windows": [],
"funding_needs": [
"investment"
]
},
"territory_names": [
"Maputo Província",
"Gaza",
"Inhambane",
"Manica",
"Sofala",
"Zambezia"
]
},
"fae": {
"edition": "tempo-benchmark-20261004-directory-20261110-v1",
"provenance": [
{
"reference": "benchmark de melhorias investeMoz.docx; quadro comparativo 04/10/2026",
"sha256": "065203aacfae766c62cf9c2c272ebacc61ff23bd4ff9e91535939fede87f7617",
"checked_on": "2026-11-10"
},
{
"reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; FAE",
"url": "https://www.bni.co.mz/banca-de-desenvolvimento/fae/",
"checked_on": "2026-11-10"
}
],
"draft": {
"title": "FAE — BNI /Agência do Zambeze",
"kind": "programme",
"summary": "Crédito em desembolso único, tranches ou espécie para agronegócio e empreendedorismo. Público/finalidade: Recém-graduados, MPME , cooperativas e associações lucrativas elegíveis.. Referência informativa, não concessão de financiamento. Condições e montantes no quadro de 04/10/2026: Segundo necessidade real do ciclo de produção e comercialização. Linha e processo publicados. A página não indica uma janela corrente com prazo; marcar “confirmação necessária” antes de aceitar candidatura.",
"manager_name": "BNI / Agência do Zambeze",
"source_url": "https://www.bni.co.mz/banca-de-desenvolvimento/fae/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; FAE",
"source_checked_on": "2026-11-10",
"source_status": "identified",
"coverage": "unknown",
"coverage_text": "Vale do Zambeze; projectos e actividades elegíveis na região.",
"territory_ids": [],
"sectors_scope": "unknown",
"sectors_text": "Critérios sectoriais por conferir no produto/programa; nenhuma elegibilidade automática.",
"sector_ids": [],
"resource_ids": [],
"criteria": [],
"windows": [],
"funding_needs": [
"investment",
"working_capital"
]
}
}
}
}
@@ -0,0 +1,744 @@
{
"format": "investmoz-mining-reference-v1",
"source_sha256": "7202fb5986b40e62347867c653770030683ffec2701a2921e84342f15e9f3d8a",
"source_document_name": "Minérios de Moçambique e a sua localização.docx",
"reference_date": "2026-11-11",
"title": "Mineração e cadeias de valor em Moçambique",
"minerals": [
{
"id": "MINERAL-01",
"name": "Carvão (térmico e metalúrgico)",
"locations_text": "Tete (Moatize, Changara, Mutarara, Marávia, Zumbo); ocorrências em Niassa (Lago) e Sofala",
"province_names": [
"Niassa",
"Sofala",
"Tete"
],
"source_row": "table1:row1",
"coordinates": null,
"classification": "reported_occurrence_group"
},
{
"id": "MINERAL-02",
"name": "Gás natural e petróleo",
"locations_text": "Inhambane (Pande e Temane); Cabo Delgado (Palma, Mocímboa, bacia do Rovuma); Sofala (Búzi); pesquisa na baía do Bazaruto",
"province_names": [
"Cabo Delgado",
"Inhambane",
"Sofala"
],
"source_row": "table1:row2",
"coordinates": null,
"classification": "reported_occurrence_group"
},
{
"id": "MINERAL-03",
"name": "Areias pesadas (ilmenite, zircão, rútilo, monazite)",
"locations_text": "Nampula (Moma, Angoche); Zambézia (Pebane, Chinde, Moebase); Inhambane (Jangamo); Gaza (Chibuto, Xai-Xai); Maputo (Ponta do Ouro, Inhaca)",
"province_names": [
"Gaza",
"Inhambane",
"Maputo",
"Nampula",
"Zambézia"
],
"source_row": "table1:row3",
"coordinates": null,
"classification": "reported_occurrence_group"
},
{
"id": "MINERAL-04",
"name": "Grafite",
"locations_text": "Cabo Delgado (Balama, Ancuabe); Niassa (Nipepe); Tete (Angónia)",
"province_names": [
"Cabo Delgado",
"Niassa",
"Tete"
],
"source_row": "table1:row4",
"coordinates": null,
"classification": "reported_occurrence_group"
},
{
"id": "MINERAL-05",
"name": "Terras raras, fluorite e gálio",
"locations_text": "Tete (Monte Muambe); Sofala (ocorrências aluvionares preliminares)",
"province_names": [
"Sofala",
"Tete"
],
"source_row": "table1:row5",
"coordinates": null,
"classification": "reported_occurrence_group"
},
{
"id": "MINERAL-06",
"name": "Ouro",
"locations_text": "Manica; Zambézia; Nampula; Tete; Niassa; ouro aluvial em rios como Revuè e Chua",
"province_names": [
"Manica",
"Nampula",
"Niassa",
"Tete",
"Zambézia"
],
"source_row": "table1:row6",
"coordinates": null,
"classification": "reported_occurrence_group"
},
{
"id": "MINERAL-07",
"name": "Ferro, titânio, vanádio e manganês",
"locations_text": "Manica, Tete (Chiúta), Nampula",
"province_names": [
"Manica",
"Nampula",
"Tete"
],
"source_row": "table1:row7",
"coordinates": null,
"classification": "reported_occurrence_group"
},
{
"id": "MINERAL-08",
"name": "Cobre, níquel, zinco e bauxite",
"locations_text": "Tete (Chidué, Fíngoè), Manica, Nampula (Monapo, Murrupula), Zambézia, Niassa",
"province_names": [
"Manica",
"Nampula",
"Niassa",
"Tete",
"Zambézia"
],
"source_row": "table1:row8",
"coordinates": null,
"classification": "reported_occurrence_group"
},
{
"id": "MINERAL-09",
"name": "Tantalite, lítio e outros metais raros",
"locations_text": "Zambézia (Ile, Alto Molócue); Nampula; Zambézia e Cabo Delgado em pegmatitos",
"province_names": [
"Cabo Delgado",
"Nampula",
"Zambézia"
],
"source_row": "table1:row9",
"coordinates": null,
"classification": "reported_occurrence_group"
},
{
"id": "MINERAL-10",
"name": "Pedras preciosas e semipreciosas",
"locations_text": "Cabo Delgado (rubis em Montepuez); Zambézia, Nampula, Manica, Tete (turmalina, água-marinha, granada, esmeralda)",
"province_names": [
"Cabo Delgado",
"Manica",
"Nampula",
"Tete",
"Zambézia"
],
"source_row": "table1:row10",
"coordinates": null,
"classification": "reported_occurrence_group"
},
{
"id": "MINERAL-11",
"name": "Mármore, apatite, calcário, argilas e diatomito",
"locations_text": "Cabo Delgado (mármore, Montepuez); Nampula (apatite); Sofala, Inhambane e Maputo (calcário); Maputo e Gaza (diatomito)",
"province_names": [
"Cabo Delgado",
"Gaza",
"Inhambane",
"Maputo",
"Nampula",
"Sofala"
],
"source_row": "table1:row11",
"coordinates": null,
"classification": "reported_occurrence_group"
}
],
"areas": [
{
"id": "MIN-01",
"title": "Carvão: fornecimento de bens e serviços e logística",
"mineral_ids": [
"MINERAL-01"
],
"locations_text": "Tete (Moatize, Revobue); corredores de Nacala e Sena (Beira)",
"province_names": [
"Tete"
],
"market_evidence": "Produção nacional de 16,3 Mt em 2024, com previsão de mais de 22 Mt em 2026; Revobue prevê 3,5 Mt /ano, subindo para 7 Mt /ano a partir de 2032",
"value_chain": "Conteúdo local, serviços à mina, manutenção ferroviária e portuária (indicativo)",
"source_state": "A estruturar",
"indicative_location": "Moatize (Tete); porto de Nacala-a-Velha e cidade da Beira, nos corredores logísticos. Distrito do Revobue não indicado nas fontes. Indicação não oficial, a partir das fontes citadas; sem valor de georreferenciação.",
"source_fields": {
"ID InvesteMoz": "[a atribuir]",
"Título": "MIN-01: Carvão: fornecimento de bens e serviços e logística",
"Sector / subsector": "Energia e minas / carvão e logística",
"Localização": "Tete (Moatize, Revobue); corredores de Nacala e Sena (Beira)",
"Localização indicativa (distrito, não oficial)": "Moatize (Tete); porto de Nacala-a-Velha e cidade da Beira, nos corredores logísticos. Distrito do Revobue não indicado nas fontes. Indicação não oficial, a partir das fontes citadas; sem valor de georreferenciação.",
"Georreferenciação": "[a confirmar]",
"Evidência de mercado": "Produção nacional de 16,3 Mt em 2024, com previsão de mais de 22 Mt em 2026; Revobue prevê 3,5 Mt /ano, subindo para 7 Mt /ano a partir de 2032.",
"Modelo possível": "Conteúdo local, serviços à mina, manutenção ferroviária e portuária (indicativo)",
"Promotor e ponto focal": "[a confirmar]",
"DUAT ou direito de uso": "[a confirmar]",
"Estudos de viabilidade e técnicos": "[a confirmar]",
"Licenças e autorizações": "[a confirmar]",
"Modelo contratual": "[a confirmar]",
"CAPEX (com data-base)": "[a confirmar]",
"Estado": "A estruturar",
"": ""
},
"source_row": "table3:row1",
"coordinates": null,
"availability": "reference_only",
"promotion_ready": false
},
{
"id": "MIN-02",
"title": "Grafite: cadeia de valor para baterias",
"mineral_ids": [
"MINERAL-04"
],
"locations_text": "Cabo Delgado (Balama, Ancuabe); Niassa (Nipepe)",
"province_names": [
"Cabo Delgado",
"Niassa"
],
"market_evidence": "4.º produtor mundial em 2024; Balama com capacidade nominal de 350 000 t/ano; produção caiu 64% em 2024",
"value_chain": "Beneficiamento e parcerias com operadores (indicativo)",
"source_state": "A estruturar",
"indicative_location": "Balama e Ancuabe (Cabo Delgado); Nipepe (Niassa). Indicação não oficial, a partir das fontes citadas; sem valor de georreferenciação.",
"source_fields": {
"ID InvesteMoz": "[a atribuir]",
"Título": "MIN-02: Grafite: cadeia de valor para baterias",
"Sector / subsector": "Minas / grafite e materiais para baterias",
"Localização": "Cabo Delgado (Balama, Ancuabe); Niassa (Nipepe)",
"Localização indicativa (distrito, não oficial)": "Balama e Ancuabe (Cabo Delgado); Nipepe (Niassa). Indicação não oficial, a partir das fontes citadas; sem valor de georreferenciação.",
"Georreferenciação": "[a confirmar]",
"Evidência de mercado": "4.º produtor mundial em 2024; Balama com capacidade nominal de 350 000 t/ano; produção caiu 64% em 2024.",
"Modelo possível": "Beneficiamento e parcerias com operadores (indicativo)",
"Promotor e ponto focal": "[a confirmar]",
"DUAT ou direito de uso": "[a confirmar]",
"Estudos de viabilidade e técnicos": "[a confirmar]",
"Licenças e autorizações": "[a confirmar]",
"Modelo contratual": "[a confirmar]",
"CAPEX (com data-base)": "[a confirmar]",
"Estado": "A estruturar",
"Fonte e data": "Fontes públicas: USGS; Diário Económico. Actualizado em 11/11/2026."
},
"source_row": "table3:row2",
"coordinates": null,
"availability": "reference_only",
"promotion_ready": false
},
{
"id": "MIN-03",
"title": "Areias pesadas e terras raras: processamento local",
"mineral_ids": [
"MINERAL-03",
"MINERAL-05"
],
"locations_text": "Nampula (Moma); Tete (Monte Muambe); Sofala (fase preliminar)",
"province_names": [
"Nampula",
"Sofala",
"Tete"
],
"market_evidence": "Moçambique é o 2.º produtor mundial de ilmenite; a monazite de Moma é exportada como concentrado, sem separação no país; Monte Muambe tem recurso de 13,6 Mt com 2,42% de óxidos de terras raras",
"value_chain": "Separação e refinação de terras raras, parcerias com operadores (indicativo)",
"source_state": "A estruturar",
"indicative_location": "Moma (Nampula); Monte Muambe (Tete). Distrito de Monte Muambe e localização em Sofala não indicados nas fontes. Indicação não oficial, a partir das fontes citadas; sem valor de georreferenciação.",
"source_fields": {
"ID InvesteMoz": "[a atribuir]",
"Título": "MIN-03: Areias pesadas e terras raras: processamento local",
"Sector / subsector": "Minas / areias pesadas e terras raras",
"Localização": "Nampula (Moma); Tete (Monte Muambe); Sofala (fase preliminar)",
"Localização indicativa (distrito, não oficial)": "Moma (Nampula); Monte Muambe (Tete). Distrito de Monte Muambe e localização em Sofala não indicados nas fontes. Indicação não oficial, a partir das fontes citadas; sem valor de georreferenciação.",
"Georreferenciação": "[a confirmar]",
"Evidência de mercado": "2.º produtor mundial de ilmenite em 2024; a monazite de Moma é exportada como concentrado, sem separação no país; Monte Muambe tem recurso de 13,6 Mt com 2,42% de óxidos totais de terras raras.",
"Modelo possível": "Separação e refinação de terras raras, parcerias com operadores (indicativo)",
"Promotor e ponto focal": "[a confirmar]",
"DUAT ou direito de uso": "[a confirmar]",
"Estudos de viabilidade e técnicos": "[a confirmar]",
"Licenças e autorizações": "[a confirmar]",
"Modelo contratual": "[a confirmar]",
"CAPEX (com data-base)": "[a confirmar]",
"Estado": "A estruturar",
"": ""
},
"source_row": "table3:row3",
"coordinates": null,
"availability": "reference_only",
"promotion_ready": false
},
{
"id": "MIN-04",
"title": "Gás natural: GNL e energia",
"mineral_ids": [
"MINERAL-02"
],
"locations_text": "Inhambane (Pande e Temane); Cabo Delgado (Coral Sul FLNG); Maputo (terminal GNL da Matola)",
"province_names": [
"Cabo Delgado",
"Inhambane",
"Maputo"
],
"market_evidence": "Pande e Temane em declínio, com fim da extracção previsto para 2030; terminal da Matola visa fecho financeiro até ao fim de 2027; Coral Sul com 3,4 Mtpa",
"value_chain": "Infra-estrutura de GNL, geração de electricidade, concessão ou parceria (indicativo)",
"source_state": "A estruturar",
"indicative_location": "Inhassoro (Inhambane, para Pande e Temane); Palma e Mocímboa (Cabo Delgado, para o gás da bacia do Rovuma); Matola (Maputo, para o terminal de GNL). Distrito do Coral Sul FLNG não indicado nas fontes. Indicação não oficial, a partir das fontes citadas; sem valor de georreferenciação.",
"source_fields": {
"ID InvesteMoz": "[a atribuir]",
"Título": "MIN-04: Gás natural: GNL e energia",
"Sector / subsector": "Energia / gás natural e GNL",
"Localização": "Inhambane (Pande e Temane); Cabo Delgado (Coral Sul FLNG); Maputo (terminal GNL da Matola)",
"Localização indicativa (distrito, não oficial)": "Inhassoro (Inhambane, para Pande e Temane); Palma e Mocímboa (Cabo Delgado, para o gás da bacia do Rovuma); Matola (Maputo, para o terminal de GNL). Distrito do Coral Sul FLNG não indicado nas fontes. Indicação não oficial, a partir das fontes citadas; sem valor de georreferenciação.",
"Georreferenciação": "[a confirmar]",
"Evidência de mercado": "Pande e Temane em declínio, com fim da extracção previsto para 2030; terminal da Matola visa fecho financeiro até ao fim de 2027; Coral Sul com capacidade de 3,4 Mtpa .",
"Modelo possível": "Infra-estrutura de GNL, geração de electricidade, concessão ou parceria (indicativo)",
"Promotor e ponto focal": "[a confirmar]",
"DUAT ou direito de uso": "[a confirmar]",
"Estudos de viabilidade e técnicos": "[a confirmar]",
"Licenças e autorizações": "[a confirmar]",
"Modelo contratual": "[a confirmar]",
"CAPEX (com data-base)": "[a confirmar]",
"Estado": "A estruturar",
"": ""
},
"source_row": "table3:row4",
"coordinates": null,
"availability": "reference_only",
"promotion_ready": false
},
{
"id": "MIN-05",
"title": "Gemas: rubis e pedras semipreciosas",
"mineral_ids": [
"MINERAL-10"
],
"locations_text": "Cabo Delgado (Montepuez); Zambézia, Nampula, Manica, Tete",
"province_names": [
"Cabo Delgado",
"Manica",
"Nampula",
"Tete",
"Zambézia"
],
"market_evidence": "Produção de rubi de 5,10 milhões de quilates em 2025 (mais 29%) e 3,18 milhões no primeiro semestre de 2026; cerca de 70% é exportada, sobretudo para os Emirados Árabes Unidos (Governo; USGS).",
"value_chain": "Lapidação, certificação e comercialização (indicativo)",
"source_state": "A estruturar",
"indicative_location": "Montepuez (Cabo Delgado). Distritos das restantes ocorrências não indicados nas fontes. Indicação não oficial, a partir das fontes citadas; sem valor de georreferenciação.",
"source_fields": {
"ID InvesteMoz": "[a atribuir]",
"Título": "MIN-05: Gemas: rubis e pedras semipreciosas",
"Sector / subsector": "Minas / gemas",
"Localização": "Cabo Delgado (Montepuez); Zambézia, Nampula, Manica, Tete",
"Localização indicativa (distrito, não oficial)": "Montepuez (Cabo Delgado). Distritos das restantes ocorrências não indicados nas fontes. Indicação não oficial, a partir das fontes citadas; sem valor de georreferenciação.",
"Georreferenciação": "[a confirmar]",
"Evidência de mercado": "Produção de rubi segundo o Ministério dos Recursos Minerais e Energia: 3,95 milhões de quilates em 2023, 4,14 em 2024 e 5,10 em 2025 (aumento de 29%; 123% da meta). Em 2026, 3,18 milhões de quilates no primeiro semestre (mais 61%), cerca de 78% da meta anual de 4,06 milhões; cerca de 70% da produção é exportada. Outra fonte indica valores mais baixos para 2023 (2,71 milhões) e 2024 (3,95 milhões), por isso os valores de anos anteriores podem ter sido revistos. Não encontrei dados de produção das outras gemas. O Montepuez Ruby Mining (Gemfields 75%, Mwiriti 25%) exportou 1,3 milhões de quilates em 2023, com receitas de leilão de 151,3 milhões de dólares nesse ano e de pelo menos 49,9 milhões de dólares em 2025. O USGS indica que os rubis são exportados sobretudo para os Emirados Árabes Unidos.",
"Modelo possível": "Lapidação, certificação e comercialização (indicativo)",
"Promotor e ponto focal": "[a confirmar]",
"DUAT ou direito de uso": "[a confirmar]",
"Estudos de viabilidade e técnicos": "[a confirmar]",
"Licenças e autorizações": "[a confirmar]",
"Modelo contratual": "[a confirmar]",
"CAPEX (com data-base)": "[a confirmar]",
"Estado": "A estruturar",
"": ""
},
"source_row": "table3:row5",
"coordinates": null,
"availability": "reference_only",
"promotion_ready": false
},
{
"id": "MIN-06",
"title": "Minerais industriais e materiais de construção",
"mineral_ids": [
"MINERAL-11"
],
"locations_text": "Cabo Delgado (mármore); Nampula (apatite); Sofala, Inhambane e Maputo (calcário); Maputo e Gaza (diatomito)",
"province_names": [
"Cabo Delgado",
"Gaza",
"Inhambane",
"Maputo",
"Nampula",
"Sofala"
],
"market_evidence": "Ocorrências identificadas; sem dados de produção confirmados neste documento",
"value_chain": "Cimento, cerâmica, agricultura (indicativo)",
"source_state": "A estruturar",
"indicative_location": "Montepuez (Cabo Delgado, mármore); Manhiça, Boane, Macia e Manjacaze (Maputo e Gaza, diatomito). Distritos das restantes ocorrências não indicados nas fontes. Indicação não oficial, a partir das fontes citadas; sem valor de georreferenciação.",
"source_fields": {
"ID InvesteMoz": "[a atribuir]",
"Título": "MIN-06: Minerais industriais e materiais de construção",
"Sector / subsector": "Minas / minerais industriais",
"Localização": "Cabo Delgado (mármore); Nampula (apatite); Sofala, Inhambane e Maputo (calcário); Maputo e Gaza (diatomito)",
"Localização indicativa (distrito, não oficial)": "Montepuez (Cabo Delgado, mármore); Manhiça, Boane, Macia e Manjacaze (Maputo e Gaza, diatomito). Distritos das restantes ocorrências não indicados nas fontes. Indicação não oficial, a partir das fontes citadas; sem valor de georreferenciação.",
"Georreferenciação": "[a confirmar]",
"Evidência de mercado": "Ocorrências identificadas; sem dados de produção confirmados neste documento.",
"Modelo possível": "Cimento, cerâmica, agricultura (indicativo)",
"Promotor e ponto focal": "[a confirmar]",
"DUAT ou direito de uso": "[a confirmar]",
"Estudos de viabilidade e técnicos": "[a confirmar]",
"Licenças e autorizações": "[a confirmar]",
"Modelo contratual": "[a confirmar]",
"CAPEX (com data-base)": "[a confirmar]",
"Estado": "A estruturar",
"": ""
},
"source_row": "table3:row6",
"coordinates": null,
"availability": "reference_only",
"promotion_ready": false
},
{
"id": "MIN-07",
"title": "Ouro: exploração e comercialização",
"mineral_ids": [
"MINERAL-06"
],
"locations_text": "Manica; Zambézia; Nampula; Tete; Niassa; ouro aluvial em rios como Revuè e Chua",
"province_names": [
"Manica",
"Nampula",
"Niassa",
"Tete",
"Zambézia"
],
"market_evidence": "Produção nacional de 1 349 kg em 2025 (queda de 17%) e meta de 1 723 kg para 2026, que seria recorde; Manica é a província que mais contribui (Governo).",
"value_chain": "Parcerias com operadores e mineração artesanal organizada (indicativo)",
"source_state": "A estruturar",
"indicative_location": "Rios Revuè, Inhamucarara, Musa, Chimesi, Mimosa e Chua (sem distritos indicados nas fontes). Indicação não oficial, a partir das fontes citadas; sem valor de georreferenciação.",
"source_fields": {
"ID InvesteMoz": "[a atribuir]",
"Título": "MIN-07: Ouro: exploração e comercialização",
"Sector / subsector": "Minas / ouro",
"Localização": "Manica; Zambézia; Nampula; Tete; Niassa; ouro aluvial em rios como Revuè e Chua",
"Localização indicativa (distrito, não oficial)": "Rios Revuè, Inhamucarara, Musa, Chimesi, Mimosa e Chua (sem distritos indicados nas fontes). Indicação não oficial, a partir das fontes citadas; sem valor de georreferenciação.",
"Georreferenciação": "[a confirmar]",
"Evidência de mercado": "Produção nacional segundo o Governo: 1 263 kg em 2022; 1 641 kg em 2024 (recorde); 1 349 kg em 2025 (queda de 17%, por suspensão da mineração em Manica); meta de 1 723 kg para 2026, com 713 kg no primeiro semestre (queda de 12%). O USGS (via CEIC) regista 1 666 kg em 2023 e 1 626 kg em 2024, valor ligeiramente diferente do do Governo para 2024. Manica é a província que mais contribui para a produção; em Setembro de 2025 o Governo suspendeu a mineração em Manica, e 30 das 41 empresas suspensas foram autorizadas a retomar em Agosto de 2026.",
"Modelo possível": "Parcerias com operadores e mineração artesanal organizada (indicativo)",
"Promotor e ponto focal": "[a confirmar]",
"DUAT ou direito de uso": "[a confirmar]",
"Estudos de viabilidade e técnicos": "[a confirmar]",
"Licenças e autorizações": "[a confirmar]",
"Modelo contratual": "[a confirmar]",
"CAPEX (com data-base)": "[a confirmar]",
"Estado": "A estruturar",
"": ""
},
"source_row": "table3:row7",
"coordinates": null,
"availability": "reference_only",
"promotion_ready": false
},
{
"id": "MIN-08",
"title": "Ferro, titânio, vanádio e manganês",
"mineral_ids": [
"MINERAL-07"
],
"locations_text": "Manica, Tete (Chiúta), Nampula",
"province_names": [
"Manica",
"Nampula",
"Tete"
],
"market_evidence": "Ocorrências identificadas; titânio associado às areias pesadas (concentrados de titânio cresceram 4% em 2024, USGS); sem dados de produção de ferro, vanádio e manganês.",
"value_chain": "Exploração e parcerias com operadores (indicativo)",
"source_state": "A estruturar",
"indicative_location": "Chiúta (Tete). Distritos de Manica e Nampula não indicados nas fontes. Indicação não oficial, a partir das fontes citadas; sem valor de georreferenciação.",
"source_fields": {
"ID InvesteMoz": "[a atribuir]",
"Título": "MIN-08: Ferro, titânio, vanádio e manganês",
"Sector / subsector": "Minas / metais ferrosos",
"Localização": "Manica, Tete (Chiúta), Nampula",
"Localização indicativa (distrito, não oficial)": "Chiúta (Tete). Distritos de Manica e Nampula não indicados nas fontes. Indicação não oficial, a partir das fontes citadas; sem valor de georreferenciação.",
"Georreferenciação": "[a confirmar]",
"Evidência de mercado": "Ocorrências identificadas; titânio associado às areias pesadas (concentrados de titânio cresceram 4% em 2024, USGS); sem dados de produção de ferro, vanádio e manganês.",
"Modelo possível": "Exploração e parcerias com operadores (indicativo)",
"Promotor e ponto focal": "[a confirmar]",
"DUAT ou direito de uso": "[a confirmar]",
"Estudos de viabilidade e técnicos": "[a confirmar]",
"Licenças e autorizações": "[a confirmar]",
"Modelo contratual": "[a confirmar]",
"CAPEX (com data-base)": "[a confirmar]",
"Estado": "A estruturar",
"": ""
},
"source_row": "table3:row8",
"coordinates": null,
"availability": "reference_only",
"promotion_ready": false
},
{
"id": "MIN-09",
"title": "Cobre, níquel, zinco e bauxite",
"mineral_ids": [
"MINERAL-08"
],
"locations_text": "Tete (Chidué, Fíngoè), Manica, Nampula (Monapo, Murrupula), Zambézia, Niassa",
"province_names": [
"Manica",
"Nampula",
"Niassa",
"Tete",
"Zambézia"
],
"market_evidence": "Ocorrências de metais básicos associados a ouro, vanádio e prata; USGS regista bauxite e alumínio entre os produtos do país, com alumínio exportado para Singapura; sem dados de produção de cobre, níquel e zinco.",
"value_chain": "Pesquisa e exploração, parcerias com operadores (indicativo)",
"source_state": "A estruturar",
"indicative_location": "Chidué e Fíngoè (Tete); Monapo e Murrupula (Nampula). Indicação não oficial, a partir das fontes citadas; sem valor de georreferenciação.",
"source_fields": {
"ID InvesteMoz": "[a atribuir]",
"Título": "MIN-09: Cobre, níquel, zinco e bauxite",
"Sector / subsector": "Minas / metais básicos e bauxite",
"Localização": "Tete (Chidué, Fíngoè), Manica, Nampula (Monapo, Murrupula), Zambézia, Niassa",
"Localização indicativa (distrito, não oficial)": "Chidué e Fíngoè (Tete); Monapo e Murrupula (Nampula). Indicação não oficial, a partir das fontes citadas; sem valor de georreferenciação.",
"Georreferenciação": "[a confirmar]",
"Evidência de mercado": "Ocorrências de metais básicos associados a ouro, vanádio e prata; USGS regista bauxite e alumínio entre os produtos do país, com alumínio exportado para Singapura; sem dados de produção de cobre, níquel e zinco.",
"Modelo possível": "Pesquisa e exploração, parcerias com operadores (indicativo)",
"Promotor e ponto focal": "[a confirmar]",
"DUAT ou direito de uso": "[a confirmar]",
"Estudos de viabilidade e técnicos": "[a confirmar]",
"Licenças e autorizações": "[a confirmar]",
"Modelo contratual": "[a confirmar]",
"CAPEX (com data-base)": "[a confirmar]",
"Estado": "A estruturar",
"": ""
},
"source_row": "table3:row9",
"coordinates": null,
"availability": "reference_only",
"promotion_ready": false
},
{
"id": "MIN-10",
"title": "Tantalite, lítio e outros metais raros",
"mineral_ids": [
"MINERAL-09"
],
"locations_text": "Zambézia (Ile, Alto Molócue); Nampula; Zambézia e Cabo Delgado em pegmatitos",
"province_names": [
"Cabo Delgado",
"Nampula",
"Zambézia"
],
"market_evidence": "6.º produtor mundial de tântalo em 2024 (USGS); depósitos de tantalite subexplorados na Zambézia; lítio em pegmatitos; o Diário Económico refere que a Britannica indica que Moçambique poderá ter as maiores reservas mundiais de tantalite.",
"value_chain": "Pesquisa, exploração e beneficiamento, parcerias com operadores (indicativo)",
"source_state": "A estruturar",
"indicative_location": "Ile e Alto Molócue (Zambézia). Indicação não oficial, a partir das fontes citadas; sem valor de georreferenciação.",
"source_fields": {
"ID InvesteMoz": "[a atribuir]",
"Título": "MIN-10: Tantalite, lítio e outros metais raros",
"Sector / subsector": "Minas / metais raros e críticos",
"Localização": "Zambézia (Ile, Alto Molócue); Nampula; Zambézia e Cabo Delgado em pegmatitos",
"Localização indicativa (distrito, não oficial)": "Ile e Alto Molócue (Zambézia). Indicação não oficial, a partir das fontes citadas; sem valor de georreferenciação.",
"Georreferenciação": "[a confirmar]",
"Evidência de mercado": "6.º produtor mundial de tântalo em 2024 (USGS); depósitos de tantalite subexplorados na Zambézia; lítio em pegmatitos; o Diário Económico refere que a Britannica indica que Moçambique poderá ter as maiores reservas mundiais de tantalite.",
"Modelo possível": "Pesquisa, exploração e beneficiamento, parcerias com operadores (indicativo)",
"Promotor e ponto focal": "[a confirmar]",
"DUAT ou direito de uso": "[a confirmar]",
"Estudos de viabilidade e técnicos": "[a confirmar]",
"Licenças e autorizações": "[a confirmar]",
"Modelo contratual": "[a confirmar]",
"CAPEX (com data-base)": "[a confirmar]",
"Estado": "A estruturar",
"": ""
},
"source_row": "table3:row10",
"coordinates": null,
"availability": "reference_only",
"promotion_ready": false
}
],
"indicators": [
{
"mineral": "Ilmenite (areias pesadas)",
"value": "2.º produtor mundial em 2024, cerca de 21% da produção global; Moma tem cerca de 200 milhões de toneladas de ilmenite em recursos",
"source": "USGS (2024); Diário Económico",
"source_row": "table2:row1"
},
{
"mineral": "Zircão",
"value": "3.º produtor mundial em 2024, cerca de 10% da produção global",
"source": "USGS (2024)",
"source_row": "table2:row2"
},
{
"mineral": "Concentrados de titânio",
"value": "Produção aumentou 4% em 2024",
"source": "USGS (2024)",
"source_row": "table2:row3"
},
{
"mineral": "Grafite",
"value": "4.º produtor mundial em 2024 (cerca de 3%); produção caiu 64% após interrupções em Ancuabe e Balama; Balama tem capacidade nominal de 350 000 t/ano",
"source": "USGS (2024); Diário Económico",
"source_row": "table2:row4"
},
{
"mineral": "Berílio e tântalo",
"value": "5.º produtor mundial de berílio (1%) e 6.º de tântalo (2%) em 2024",
"source": "USGS (2024)",
"source_row": "table2:row5"
},
{
"mineral": "Terras raras (Monte Muambe)",
"value": "Recurso de 13,6 milhões de toneladas com 2,42% de óxidos totais de terras raras (código JORC)",
"source": "Diário Económico",
"source_row": "table2:row6"
},
{
"mineral": "Produção mineral total",
"value": "27,3 milhões de toneladas em 2023, contra 20,9 milhões em 2022",
"source": "CEIC (2023)",
"source_row": "table2:row7"
},
{
"mineral": "Carvão (Tete)",
"value": "Produção nacional, concentrada em Tete: 14,8 milhões de toneladas em 2022, 14,9 em 2023 e 16,3 em 2024; previsão do Governo de 19 milhões em 2025 e mais de 22 milhões em 2026. A mina de Moatize (Vulcan) é a maior; Revobue prevê 3,5 milhões de t/ano na fase inicial",
"source": "Club of Mozambique / Africa-Press (PESOE 2026); Without Borders Energy",
"source_row": "table2:row8"
},
{
"mineral": "Gás natural (Pande e Temane)",
"value": "Produção acumulada de 79,48 mil milhões de m³ até Outubro de 2025; reservas provadas da concessão PPA de 327,9 mil milhões de pés cúbicos em Junho de 2026 (440,6 em Junho de 2025); a produção total da Sasol em Moçambique caiu 7% no exercício de 2026; fim da extracção previsto para 2030",
"source": "Club of Mozambique (Lusa); MZ News (Sasol 20-F)",
"source_row": "table2:row9"
},
{
"mineral": "Gás natural (Coral Sul FLNG)",
"value": "Capacidade de liquefacção de cerca de 3,4 milhões de toneladas por ano (Bacia do Rovuma, Cabo Delgado)",
"source": "Eni",
"source_row": "table2:row10"
},
{
"mineral": "Ouro",
"value": "Produção nacional segundo o Governo: 1 263 kg em 2022; 1 641 kg em 2024 (recorde); 1 349 kg em 2025 (queda de 17%, por suspensão da mineração em Manica); meta de 1 723 kg para 2026, com 713 kg no primeiro semestre (queda de 12%). O USGS (via CEIC) regista 1 666 kg em 2023 e 1 626 kg em 2024, valor ligeiramente diferente do do Governo para 2024.",
"source": "Ministério dos Recursos Minerais e Energia (via Club of Mozambique/Lusa e 360 Mozambique/Diário Económico); USGS (via CEIC)",
"source_row": "table2:row11"
},
{
"mineral": "Rubis e outras gemas",
"value": "Produção de rubi segundo o Ministério dos Recursos Minerais e Energia: 3,95 milhões de quilates em 2023, 4,14 em 2024 e 5,10 em 2025 (aumento de 29%; 123% da meta). Em 2026, 3,18 milhões de quilates no primeiro semestre (mais 61%), cerca de 78% da meta anual de 4,06 milhões; cerca de 70% da produção é exportada. Outra fonte indica valores mais baixos para 2023 (2,71 milhões) e 2024 (3,95 milhões), por isso os valores de anos anteriores podem ter sido revistos. Não encontrei dados de produção das outras gemas.",
"source": "Ministério dos Recursos Minerais e Energia (via Club of Mozambique/Lusa e 360 Mozambique); Beyond The Bay",
"source_row": "table2:row12"
}
],
"projects": [
"Balama (Cabo Delgado): a maior mina de grafite de África, operada pela Syrah Resources .",
"Moma (Nampula): mina de areias pesadas operada pela Kenmare Resources, com ilmenite, zircão e rútilo.",
"Monte Muambe (Tete): projecto de terras raras, fluorite e gálio associado.",
"Pande e Temane (Inhambane): jazigos de gás natural, com gasoduto para a África do Sul.",
"Coral Sul FLNG (Cabo Delgado): primeira unidade flutuante de GNL instalada em águas ultraprofundas, na bacia do Rovuma, com capacidade de liquefacção de cerca de 3,4 milhões de toneladas por ano. É operada pela Eni em nome dos parceiros da Área 4 (ExxonMobil, CNPC, Galp, KOGAS e ENH).",
"Revobue (Tete): mina de carvão inaugurada em Março de 2026, com o grupo indiano Jindal como principal accionista. Prevê produzir 3,5 milhões de toneladas por ano na fase inicial e 7 milhões a partir de 2032, com vida útil de 35 anos. O carvão será exportado pela linha de Sena até ao porto da Beira."
],
"risks": [
"Segundo a regra de publicação do InvesteMoz, uma oportunidade só recebe o estado \"Pronta para promoção\" quando estiverem documentados, em simultâneo, os seis requisitos abaixo. Nenhum está confirmado para as áreas listadas, por isso todas ficam \"A estruturar\".",
"Situação fundiária (DUAT) ou direito de uso aplicável.",
"Promotor e ponto focal.",
"Estudos de viabilidade e técnicos requeridos.",
"Licenças e autorizações relevantes.",
"Modelo contratual aprovado.",
"CAPEX actualizado, com data-base.",
"Riscos e pontos de diligência identificados nas fontes",
"Volatilidade de produção: a produção de grafite caiu 64% em 2024 e as vendas de carvão caíram 35% no primeiro trimestre de 2025, por paragens de minas e perturbações ferroviárias.",
"Declínio do gás: as reservas provadas de Pande e Temane caíram 25,6% num ano.",
"Comunidades e ambiente: há críticas à poluição e às detonações em Moatize, e o Revobue exige o reassentamento de cinco aldeias.",
"Ouro e licenciamento: a mineração foi suspensa em Manica desde 30 de Setembro de 2025 e a produção de 2025 caiu 17%; também houve paragens no Niassa para regularização de licenças.",
"Rubis e mineração ilegal: o Montepuez Ruby Mining refere centenas de mineiros ilegais por dia na sua concessão, o que atrasou a segunda fábrica de processamento e o leilão de 2025; a produção da terceira maior produtora está suspensa.",
"Dados: faltam valores de produção de outras gemas e de gás por ano; os dados do EITI e do Ministério dos Recursos Minerais e Energia devem ser usados para os confirmar. Os valores de ouro e de rubi divergem entre fontes.",
"Próximos passos para publicação",
"Confirmar a entidade responsável, validar os dados com o Ministério dos Recursos Minerais e Energia, georreferenciar as áreas e obter validação jurídica e editorial antes de publicar."
],
"sources": [
"EITI Moçambique – Ocorrência de minerais em Moçambique (anexos 2 e 4), eiti.org.",
"Escola MZ – Principais rochas e minerais e suas áreas de ocorrência (escolamz.com).",
"Fluxo Arte – Distribuição por província de recursos naturais (mozconteudo.blogspot.com).",
"Diário Económico – A janela estreita entre geologia e geoeconomia, Parte 2 (23/07/2026).",
"USGS – Mozambique, National Minerals Information Center (usgs.gov).",
"CEIC – Moçambique: Produção de minerais (ceicdata.com).",
"Ministério dos Recursos Minerais e Energia – Relatório Estatístico da Indústria Extractiva 2024; EITI – Relatório ITIE 2023-2024 (eiti.org).",
"Club of Mozambique – Mozambique expects coal production to reach record highs next year (26/11/2025); Mozambique prepares alternative to declining Pande and Temane gas production (05/11/2026).",
"Without Borders Energy – Mozambique Coal; MZ News – Reservas de gás da Sasol caem 25,6% (08/11/2026); Eni – Coral Sul FLNG Key Facts .",
"Club of Mozambique – Mozambique: Chapo inaugurates coal mine in Tete (16/03/2026).",
"Integrity Magazine – Da Vale a Vulcan : comunidades de Moatize (15/09/2024).",
"Club of Mozambique – Mozambique: Gold production rises 4% in Q1 to 283 kilogrammes (17/06/2026); 360 Mozambique – Mozambique Gold Output Falls 12% in First Half on Mining Suspensions (18/09/2026); CEIC – Mozambique Gold Production (usgs.gov/ceicdata.com).",
"Club of Mozambique – Mozambique: ruby production soars past five million carats in 2025 (09/04/2026); Montepuez Ruby Mining second plant set for 2025 (29/08/2024); 360 Mozambique – Ruby Production Increased by 29% (09/04/2026); Beyond The Bay – Mozambique ruby production surges in first half of 2026 (24/09/2026)."
],
"original_paragraphs": [
"Recursos Minerais de Moçambique: Referência de Oportunidades de Investimento",
"Ficha de referência para o portal InvesteMoz",
"Estado: A estruturar (informação de referência; nenhuma oportunidade classificada como \"Pronta para promoção\") Fonte: fontes públicas listadas na secção 8 .0 Data de actualização: 11 de Novembro de 2026 Entidade responsável / ponto focal: [a confirmar antes da publicação] Natureza: informação orientadora; não constitui oferta nem aconselhamento de investimento. Os dados carecem de validação institucional e jurídica.",
"1.0 Resumo para o Investidor",
"Moçambique combina carvão e gás de escala exportadora, grafite e areias pesadas de relevância mundial, terras raras, rubis e minerais industriais, distribuídos por todas as províncias. Segundo o USGS, a mineração contribuiu com cerca de 15% do PIB em 2024. Este documento organiza a informação pública disponível por minério e localização e identifica áreas de oportunidade (fornecimento de serviços, logística, processamento local e energia), com as respectivas condições de prontidão e riscos.",
"2.0 Principais Minérios e Localização",
"Minérios por província e zona de ocorrência",
"3.0 Projectos de Destaque",
"Balama (Cabo Delgado): a maior mina de grafite de África, operada pela Syrah Resources .",
"Moma (Nampula): mina de areias pesadas operada pela Kenmare Resources, com ilmenite, zircão e rútilo.",
"Monte Muambe (Tete): projecto de terras raras, fluorite e gálio associado.",
"Pande e Temane (Inhambane): jazigos de gás natural, com gasoduto para a África do Sul.",
"Coral Sul FLNG (Cabo Delgado): primeira unidade flutuante de GNL instalada em águas ultraprofundas, na bacia do Rovuma, com capacidade de liquefacção de cerca de 3,4 milhões de toneladas por ano. É operada pela Eni em nome dos parceiros da Área 4 (ExxonMobil, CNPC, Galp, KOGAS e ENH).",
"Revobue (Tete): mina de carvão inaugurada em Março de 2026, com o grupo indiano Jindal como principal accionista. Prevê produzir 3,5 milhões de toneladas por ano na fase inicial e 7 milhões a partir de 2032, com vida útil de 35 anos. O carvão será exportado pela linha de Sena até ao porto da Beira.",
"4.0 Reservas e Produção Anual",
"Segundo o Serviço Geológico dos EUA (USGS), a indústria mineira contribuiu com cerca de 15% do PIB de Moçambique em 2024. Os valores abaixo vêm de fontes públicas e referem-se aos anos indicados.",
"Produção e recursos dos principais minérios",
"",
"A s quantidades das outras gemas (além do rubi) constam do Relatório Estatístico da Indústria Extractiva 2024, do Ministério dos Recursos Minerais e Energia, e do Relatório ITIE 2023-2024. Não as incluí aqui por não ter conseguido confirmar os valores exactos. Os valores de carvão são nacionais; a maior parte provém de Tete. Os valores de gás por ano de produção não foram encontrados nas fontes consultadas. Os valores de ouro e de rubi divergem entre fontes, e a produção artesanal pode não estar totalmente registada.",
"5.0 Oportunidades de Investimento",
"A tabela cruza a informação das secções anteriores com áreas onde a literatura pública aponta espaço para investimento. Os modelos são indicativos e não representam projectos aprovados nem promotores confirmados.",
"Áreas de oportunidade por cadeia de valor",
"6.0 Fichas Individuais InvesteMoz",
"Cada ficha segue os campos de publicação do InvesteMoz. Os campos marcados \"[a confirmar]\" devem ser preenchidos e documentados antes de a oportunidade passar de \"A estruturar\" para \"Pronta para promoção\".",
"Ficha 1: Carvão: fornecimento de bens e serviços e logística",
"Ficha 2: Grafite: cadeia de valor para baterias",
"Ficha 3: Areias pesadas e terras raras: processamento local",
"Ficha 4: Gás natural: GNL e energia",
"Ficha 5: Gemas: rubis e pedras semipreciosas",
"Ficha 6: Minerais industriais e materiais de construção",
"Ficha 7: Ouro: exploração e comercialização",
"Ficha 8: Ferro, titânio, vanádio e manganês",
"Ficha 9: Cobre, níquel, zinco e bauxite",
"Ficha 10: Tantalite, lítio e outros metais raros",
"7 .0 Prontidão para Publicação e Riscos",
"Segundo a regra de publicação do InvesteMoz, uma oportunidade só recebe o estado \"Pronta para promoção\" quando estiverem documentados, em simultâneo, os seis requisitos abaixo. Nenhum está confirmado para as áreas listadas, por isso todas ficam \"A estruturar\".",
"Situação fundiária (DUAT) ou direito de uso aplicável.",
"Promotor e ponto focal.",
"Estudos de viabilidade e técnicos requeridos.",
"Licenças e autorizações relevantes.",
"Modelo contratual aprovado.",
"CAPEX actualizado, com data-base.",
"Riscos e pontos de diligência identificados nas fontes",
"Volatilidade de produção: a produção de grafite caiu 64% em 2024 e as vendas de carvão caíram 35% no primeiro trimestre de 2025, por paragens de minas e perturbações ferroviárias.",
"Declínio do gás: as reservas provadas de Pande e Temane caíram 25,6% num ano.",
"Comunidades e ambiente: há críticas à poluição e às detonações em Moatize, e o Revobue exige o reassentamento de cinco aldeias.",
"Ouro e licenciamento: a mineração foi suspensa em Manica desde 30 de Setembro de 2025 e a produção de 2025 caiu 17%; também houve paragens no Niassa para regularização de licenças.",
"Rubis e mineração ilegal: o Montepuez Ruby Mining refere centenas de mineiros ilegais por dia na sua concessão, o que atrasou a segunda fábrica de processamento e o leilão de 2025; a produção da terceira maior produtora está suspensa.",
"Dados: faltam valores de produção de outras gemas e de gás por ano; os dados do EITI e do Ministério dos Recursos Minerais e Energia devem ser usados para os confirmar. Os valores de ouro e de rubi divergem entre fontes.",
"Próximos passos para publicação",
"Confirmar a entidade responsável, validar os dados com o Ministério dos Recursos Minerais e Energia, georreferenciar as áreas e obter validação jurídica e editorial antes de publicar.",
"8 .0 Fontes",
"EITI Moçambique – Ocorrência de minerais em Moçambique (anexos 2 e 4), eiti.org.",
"Escola MZ – Principais rochas e minerais e suas áreas de ocorrência (escolamz.com).",
"Fluxo Arte – Distribuição por província de recursos naturais (mozconteudo.blogspot.com).",
"Diário Económico – A janela estreita entre geologia e geoeconomia, Parte 2 (23/07/2026).",
"USGS – Mozambique, National Minerals Information Center (usgs.gov).",
"CEIC – Moçambique: Produção de minerais (ceicdata.com).",
"Ministério dos Recursos Minerais e Energia – Relatório Estatístico da Indústria Extractiva 2024; EITI – Relatório ITIE 2023-2024 (eiti.org).",
"Club of Mozambique – Mozambique expects coal production to reach record highs next year (26/11/2025); Mozambique prepares alternative to declining Pande and Temane gas production (05/11/2026).",
"Without Borders Energy – Mozambique Coal; MZ News – Reservas de gás da Sasol caem 25,6% (08/11/2026); Eni – Coral Sul FLNG Key Facts .",
"Club of Mozambique – Mozambique: Chapo inaugurates coal mine in Tete (16/03/2026).",
"Integrity Magazine – Da Vale a Vulcan : comunidades de Moatize (15/09/2024).",
"Club of Mozambique – Mozambique: Gold production rises 4% in Q1 to 283 kilogrammes (17/06/2026); 360 Mozambique – Mozambique Gold Output Falls 12% in First Half on Mining Suspensions (18/09/2026); CEIC – Mozambique Gold Production (usgs.gov/ceicdata.com).",
"Club of Mozambique – Mozambique: ruby production soars past five million carats in 2025 (09/04/2026); Montepuez Ruby Mining second plant set for 2025 (29/08/2024); 360 Mozambique – Ruby Production Increased by 29% (09/04/2026); Beyond The Bay – Mozambique ruby production surges in first half of 2026 (24/09/2026)."
],
"source_instructions_are_not_product_rules": true
}
@@ -2202,3 +2202,86 @@ Dois testes frontend dirigidos passaram: contagens/proveniência desconhecida/re
**ACT-02 permanece parcial:** este recorte implementa controlo das camadas já existentes e explicitação de cobertura. Não cria camadas factuais de recursos, infra-estruturas, mão-de-obra ou potencial económico, nem declara completa a cobertura provincial/distrital. Backend, geometrias, divulgação, permissões, fontes, datas e cálculo preservados; sem importações ou escrita no activo.
**Visual pendente pela equipa:** na candidata, conferir os mapas da ficha de oportunidade, carteira e zona de investimento; ocultar/repor cada camada e ambas, conferir contagens de investimentos/marcadores, fonte/data ausentes e fichas sem coordenadas, e mudar filtros territoriais. Registar revisão, perfil, percurso, esperado/observado e evidência nesta matriz. O painel de contexto administrativo separado mantém o seu âmbito. Sem instalação ou avanço de main.
### 04/11/2026 — conferência visual da main na QA isolada e correcção do CSV provincial
Percurso executado no Edge em `http://127.0.0.2:18644`, base exclusiva `main_visual_20261104_test` no cluster QA `127.0.0.1:55441`, com actor e conteúdos sintéticos. Autenticação Fortify normal com MFA: editar, guardar v2, recarregar e conferir os valores; aprovar v3; guardar novo rascunho v4; terminar sessão e conferir publicamente apenas v3, sem o marcador do rascunho. Histórico v1–v4 preservado. Esta prova conclui o recorte visual local de Competitividade; não equivale a execução no portal remoto ou aceitação da equipa em dados reais.
Filtro apenas por Maputo Província, com sector Agronegócio: cinco oportunidades existentes mais quatro fixtures sintéticas nos níveis província/distrito/posto/localidade resultaram em nove na grelha, lista, mapa e CSV. Distrito de Matola apresentou três; regressar a toda a província recuperou nove e limpou distrito/posto/localidade. Nenhuma coordenada criada: mapa informa zero locais e nove sem coordenadas. Hash das oportunidades anteriores idêntico antes/depois. Limite administrativo desta província indisponível na QA, explicitamente indicado; cobertura cartográfica transversal continua pendente.
Detectada e corrigida divergência no CSV: coluna Província vazia apesar da localização canónica/contexto de corredor disponível. Exportação reutiliza a apresentação territorial existente, conserva as oito primeiras colunas e o escape contra fórmulas, acrescentando Localização e âmbito. Contexto do corredor indica expressamente que distrito/posto não foram indicados; não grava nem inventa localização. CSV descarregado pela interface após correcção: nove linhas, todas com província e âmbito coerentes.
Delta aprovado: 17 testes de base de dados, 609 asserções; PHPStan sem erros e Pint aprovado. Provas anteriores de 1.325 testes/28.377 asserções preservadas, sem repetição integral. Recibo com hashes e capturas: `docs/investmoz/unification/main-local-visual-qa-20261104.json`; artefactos em `outputs/main-visual-qa-20261104/`. Apenas main local e QA; sem push, escrita activa ou alteração de permissões activas. Conferência dos filtros no portal de destino e pendências gerais ACT-02/V2-07/V2-10 continuam abertas.
### 04/11/2026 — ACT-20261029-02: limites do nível imediatamente inferior e lacunas de cobertura
Continuação sequencial na main local sobre `6dde36b5`, reutilizando V2-07/V2-10, serviço de geometria, permissões e mapa único. Inventário prévio confirmou camadas/legenda/fontes já existentes e contexto limitado à selecção/antepassados; nenhuma perda confirmada. Os 12 artefactos do recibo visual anterior foram novamente conferidos por SHA256, sem alteração. Provas aceites de 1.325 testes e demais incrementos preservadas, sem repetição integral.
O catálogo solicita `include_children=1` no endpoint administrativo existente. A opção acrescenta apenas referências do nível imediatamente inferior: distritos de uma província, postos de um distrito e localidades de um posto. Sem a opção conserva o âmbito anterior. Geometrias continuam sujeitas à classificação, acesso efectivo e snapshots/hashes de divulgação existentes; não há publicação, importação ou nova autorização. O território seleccionado tem prioridade, seguido dos antepassados e subdivisões. Até 100 geometrias e 500 referências inferiores, com truncagem explícita.
Resposta acrescenta `child_territory` e `child_coverage` com nível, referências acessíveis e territórios representados, distinguindo contagem territorial de polígonos. Interface identifica subdivisão/contexto/território seleccionado, conserva fonte/edição e explicita que falta de limite na consulta não significa inexistência do território, nem terreno disponível. Nenhuma oportunidade é reposicionada e nenhuma coordenada é presumida.
Delta aprovado: 11 testes de base de dados/125 asserções (cinco novos e seis de acesso existente), nove testes frontend, PHPStan sem erros, tipos, Pint, lint e build QA aprovados. Inclui exclusão de outra província/netos, limites públicos/internos, referência eliminada, ausência de geometria, priorização e truncagem de geometrias/referências, validação do parâmetro e descarte de resposta anterior ao mudar a opção. Primeira tentativa da fixture falhou por faltar país-pai; corrigida a fixture, mantendo a integridade territorial e os logs iniciais.
Conferência visual no Edge, exclusivamente QA `127.0.0.2:18644`/`main_visual_20261104_test`: Maputo Província mostra zero limites de oito referências distritais acessíveis, lacuna explicitada, nove oportunidades mantidas sem ponto inventado. O snapshot não contém limites públicos para demonstrar visualmente os polígonos inferiores; estes foram exercitados por testes sintéticos. Recibo/hashes: `docs/investmoz/unification/map-subdivision-local-20261104.json`; captura em `outputs/map-subdivision-20261104/`.
**ACT-02 permanece parcial.** Camadas factuais de recursos, infra-estruturas, mão-de-obra e potencial económico, cobertura de dados/geometrias e conferência no portal de destino continuam pendentes. Este recorte não encerra V2-07/V2-10 nem substitui aceitação da equipa. Sem push, migração, escrita activa, importação, alteração de permissões ou reinício de serviços.
### 04/11/2026 — ACT-20261029-02: camadas de evidência pública de recursos e infra-estruturas
Incremento sequencial na main local sobre `bbc05f1d`, reutilizando V2-07/V2-10, mapa único, filtros, pontos/áreas públicos e contexto administrativo já demonstrados. Inventário de código e cópia QA antes de fixtures: 48 oportunidades publicadas e duas sínteses proactivas públicas, mas nenhuma evidência pública local das categorias resource/infrastructure. Os campos existentes de zonas e fontes editoriais não foram convertidos automaticamente em disponibilidade ou geometrias temáticas; evidências privadas não foram publicadas. Não é perda confirmada de dados nem diagnóstico do remoto. Conferidos por SHA256 os 23 artefactos dos dois recibos visuais/administrativos anteriores, todos inalterados.
A API existente de mapa acrescenta `thematic_records` a partir de `ProactiveInvestment::publicData`: somente evidência seleccionada na síntese publicada, categorias recursos/infra-estruturas e propósito local. Conserva facto, fonte, localizador, período, âmbito, cobertura, verificação registada, situação e limitações. Exclui metodologia e outras categorias; não copia o rascunho privado. Mantém a consulta territorial, limite/truncagem e autorização pública anteriores. As áreas acrescentam o identificador da oportunidade para filtragem, sem atribuir a geometria à evidência.
No mesmo mapa, o selector permite todas as oportunidades, recursos documentados ou infra-estruturas documentadas. A camada filtra apenas pontos/áreas existentes das fichas correspondentes; não alarga a pesquisa geral, não altera CSV nem cria posições. Fichas sem ponto continuam consultáveis no painel de evidências/fontes. Contagens referem fichas, não quantidade/capacidade de recursos. Âmbito da evidência distingue-se da localização da oportunidade; marcadores não representam posição exacta ou disponibilidade de recurso/infra-estrutura. Escolha recuperada na sessão do navegador, com tolerância a armazenamento indisponível/inválido. Falta de evidência, período ou fonte é explícita, sem zero presumido.
Delta aprovado: oito testes de BD/438 asserções (quatro novos e quatro de filtros provinciais), 14 testes frontend (cinco novos e nove existentes), PHPStan sem erros, tipos, Pint, lint e build QA aprovados. Inclui exclusão de rascunhos/publicação futura/acesso restrito, leitura apenas da síntese pública, fonte/período/limites, província/distrito, reutilização das coordenadas existentes, alternância/reposição de camadas, fichas sem ponto, carregamento/ausência/truncagem, escape de texto e recuperação da escolha. Provas gerais anteriores preservadas, sem repetir a bateria completa.
Conferência visual no Edge exclusivamente em `127.0.0.2:18644`, base `main_visual_20261104_test`: duas fixtures claramente sintéticas criadas apenas em QA, uma por categoria; nenhuma coordenada/geometria criada. Hash de todas as oportunidades anteriores idêntico antes/depois. Alternância entre ambas conserva pesquisa provincial e duas fichas gerais; cada camada mostra a sua ficha, fonte/período/âmbito/limites e ausência de ponto. Infra-estruturas recuperada após recarregar. Recibo: `docs/investmoz/unification/thematic-map-local-20261104.json`; inventário, hashes, DOM e capturas em `outputs/thematic-map-20261104/`. Demonstração sintética não constitui cobertura económica factual ou publicação de dados reais.
**ACT-02 permanece parcial.** Cobertura factual de recursos/infra-estruturas no destino, georreferenciação própria quando documentada, mão-de-obra, potencial económico quantitativo, restantes fontes temáticas e lacunas cartográficas continuam pendentes. Não se inferem estas camadas de descrições nem do número de oportunidades. Mantêm-se conferência remota e aceitação da equipa; V2-07/V2-10 não encerrados. Sem push, alteração activa, migração, publicação automática ou alteração de permissões/serviços.
### 04/11/2026 — ACT-20261029-02: mão-de-obra documentada e contexto económico
Incremento qualitativo na main local sobre `21ebad73`, reutilizando mapa, evidências, síntese publicada, divulgação expressa e fontes/períodos existentes. Inventário na cópia QA: duas evidências públicas locais de actividade anteriores (fichas sintéticas 350/383), nenhuma de mão-de-obra/oferta/procura; recursos/infra-estruturas da demonstração anterior conservados. As declarações labour_demands/training_capacities têm acesso EducationAccess interno; emprego/indicadores financeiros canónicos não fazem parte da publicProjection. Observações de zonas conservam divulgação/unidades/metodologia próprias. Não foram expostas nem agregadas essas fontes privadas por existir revisão interna. As fontes oficiais aceites e as decisões de divulgação anteriores não foram reabertas.
Acrescentada categoria explícita Mão-de-obra no editor e validação da evidência existente, sem migração ou reclassificação automática dos textos anteriores. Guardar, recuperar, pré-visualizar e confirmar publicação usam o percurso existente. A camada económica reutiliza apenas evidências públicas locais de actividade/oferta/procura, mantendo `source_category` original, facto, fonte, localizador, período, verificação, âmbito, cobertura e limitações. Agrupá-las para consulta é uma escolha de apresentação, não aprovação de viabilidade nem índice de potencial.
Ambas as opções entram no selector do mapa único; a escolha é recuperada na sessão. Recursos/infra-estruturas, geometrias públicas, filtros territoriais, fichas sem ponto e contagens anteriores mantêm-se. Nenhuma posição é criada ou atribuída ao recurso/mão-de-obra. Necessidade ou qualificação não equivale a trabalhador disponível; previsão de emprego não é emprego efectivo. Contexto económico não calcula retorno, score ou viabilidade. Períodos/fontes incompatíveis permanecem separados, sem totais ou zero presumido.
Delta aprovado: sete testes de BD/77 asserções (cinco do mapa, incluindo o novo caso; dois de publicação, um novo e um de acesso/fonte privada já existente), 22 testes frontend, PHPStan sem erros, tipos, Pint, lint e build QA aprovados. Teste do percurso: criar evidência workforce → recuperar → confirmar pela publicação existente → consultar mapa → alterar rascunho privado sem substituir a categoria/fonte públicas. Testes mantêm metodologia/internos fora do mapa, categorias originais, períodos distintos e ausência de quantidades/índices inferidos. Não se repetiu a bateria geral aceite.
Conferência visual no Edge exclusivamente na QA `127.0.0.2:18644`/`main_visual_20261104_test`: contexto económico reutiliza a evidência de actividade já publicada da ficha 383, sintética e explicitamente identificada. Mão-de-obra usa uma única fixture nova, também sintética; escolha e fonte recuperadas após recarregar. Nenhuma coordenada/geometria criada; hash de todas as oportunidades anteriores idêntico antes/depois. Conferidos 36 artefactos dos três recibos anteriores, todos com SHA256 inalterado. Recibo: `docs/investmoz/unification/workforce-economic-map-local-20261104.json`; inventário, DOM, capturas e hashes em `outputs/workforce-economic-map-20261104/`.
**ACT-02 permanece parcial.** Cobertura factual laboral/económica, séries, unidades e metodologia quantitativa comparável, georreferenciação própria documentada e cobertura cartográfica continuam pendentes. A demonstração sintética não mede oferta laboral nem potencial regional real. Mantêm-se conferência remota e aceitação da equipa; V2-07/V2-10 não encerrados. Sem push, migração, publicação automática, escrita activa, alteração de permissões ou reinício de serviços.
### 04/11/2026 — ACT-20261029-02: observações quantitativas públicas de emprego e contexto económico
Incremento na main local sobre `a9241ff2`. Inventário prévio: fichas de parques já conservam observações com valor, unidade, período, âmbito, população/base, metodologia, conhecimento e divulgação seleccionada. Reutilizada apenas a versão pública de `operational_details.public.integral.observations`, sem desencriptar preparação privada. Na cópia QA havia oito fichas públicas e zero observações quantitativas divulgadas; não foi declarada perda nem ausência no remoto. As 51 provas anteriores foram conferidas por SHA256 antes da ronda e novamente no fim, todas inalteradas; bateria geral aceite preservada sem repetição integral.
A API de mapa acrescenta `quantitative_context` a partir do mesmo conjunto de zonas já devolvido no contexto territorial: emprego para mão-de-obra; investimento, produção, exportações e compras locais para contexto económico. Reutiliza os filtros/autorização existentes, sem alargar a pesquisa de oportunidades nem transformar indicadores do parque em indicadores das oportunidades. Filtros específicos que já excluem esse conjunto devolvem âmbito não incluído, em vez de uma falsa medição vazia. Até 30 fichas e 200 observações; truncagem explícita. Geometrias, divulgação, fontes e versões anteriores conservadas.
O painel do mapa mostra cada valor com a sua unidade, período, âmbito, população/base, metodologia, fonte/data, versão pública e ligação à ficha. Zero documentado e valor desconhecido são distintos; precisão decimal conserva-se como texto. Divergências e metadados em falta permanecem visíveis. Emprego não é oferta de trabalhadores disponíveis; natureza prevista/efectiva depende da descrição da fonte. Não se somam populações ou períodos, não se convertem unidades/moedas e não se criam médias, índices ou previsões de retorno. Contagens de oportunidades e de observações territoriais têm âmbitos distintos. Erro, carregamento, cadastro indisponível, âmbito não consultado e ausência de observações são estados diferentes.
Verificação dirigida aprovada: 11 testes BD/99 asserções (seis novos e cinco do mapa temático existente), 18 testes frontend (três novos e 15 existentes), PHPStan sem erros, tipos, Pint, lint e compilação QA aprovados. Inclui zero/decimais, desconhecido/divergência, metadados incompletos, exclusão de preparação privada/ficha não publicada, preservação do envelope, província/distrito, limites de observações/fichas e âmbito excluído. Primeira tentativa teve uma expectativa inadequada sobre a serialização JSON antes da normalização jsonb; comparação corrigida para duas leituras efectivas do servidor. Fixture frontend com rótulo repetido corrigida; não se relaxaram regras do produto. Logs BD iniciais mantidos.
Conferência visual exclusivamente em `127.0.0.2:18644`/`main_visual_20261104_test`: uma ficha sintética nova com cinco observações. Camada laboral mostra zero previsto e emprego indirecto por determinar, em duas linhas; económica mostra 125.7500 toneladas, 100000.00 MZN e 25.50 USD em divergência, em três linhas, com as respectivas fontes e períodos. Valores exclusivamente de teste, sem aplicação económica real. Nenhuma coordenada/geometria criada; hash de todas as fichas territoriais anteriores idêntico antes/depois. Recibo e hashes: `docs/investmoz/unification/quantitative-map-local-20261104.json`; inventário, capturas e DOM em `outputs/quantitative-map-20261104/`.
**ACT-02 permanece parcial.** Cobertura factual nacional/regional, séries comparáveis, oferta laboral, natureza das grandezas e metodologia quantitativa de potencial económico continuam pendentes conforme dados aplicáveis. A demonstração sintética não é estatística real, índice regional ou diagnóstico do portal remoto. Mantêm-se conferência remota e aceitação da equipa; V2-07/V2-10 não encerrados. Sem push, instalação, escrita activa, migração, publicação automática ou alteração de permissões/serviços.
Precisão adicional demonstrada: número JSON `1234567890123.456789` recuperado sem arredondamento. O valor é extraído como texto decimal do mesmo snapshot público carregado, sem o converter num float PHP nem misturar metadados e versão com uma leitura posterior. O teste dirigido de precisão e PHPStan passaram após esta correcção.
### 04/11/2026 — ACT-20261029-02: filtros de indicador e período das observações quantitativas
Main local sobre `3f50f666`, sem push. Inventário do concluído: mantidas as camadas temáticas, hierarquia territorial, observações públicas, precisão decimal, fonte/data/versão, estados de conhecimento e limites de cobertura já demonstrados. Conferidos por SHA256 os 63 recibos JSON anteriores da pasta de unificação, todos inalterados. Não foi identificada perda neste delta; as provas aceites, incluindo 1.325 testes/28.377 asserções, não foram reabertas. A instalação anterior de `3f50f666` mantém recibo em `outputs/main-install-20261104/installation-result.json`; os estados «sem instalação» das entradas anteriores descrevem a sua execução histórica.
Próximo recorte dos filtros previstos na matriz: selecção por indicador e período de referência sobre as observações quantitativas públicas já recebidas no mapa. Sobreposição temporal inclusiva, com início e/ou fim opcionais; intervalo invertido apresenta erro. Períodos incompletos, inválidos ou invertidos da fonte continuam consultáveis sem filtro temporal e são excluídos quando este é aplicado; a data da fonte não substitui o período. A limpeza repõe as observações. Não altera a pesquisa de oportunidades, os limites de resposta ou a cobertura territorial; não soma, converte, estima nem infere comparabilidade. Escolha de implementação delimitada aos filtros da ACT-02, sem nova regra organizacional.
Delta verificado: 12 testes frontend dirigidos aprovados, tipos globais, lint do componente e build QA aprovados. Conferência visual apenas em `127.0.0.2:18644`/`main_visual_20261104_test`, reutilizando as cinco observações sintéticas anteriores, sem novas fixtures ou escrita de dados. Produção em 31/12/2025 devolve uma linha com `125.7500` toneladas; intervalo invertido mostra erro; limpar repõe cinco linhas. As 12 oportunidades do cenário QA mantêm-se. Corrigida e conferida a actualização dos campos de data durante a introdução. Recibo e hashes: `docs/investmoz/unification/quantitative-map-filters-local-20261104.json`; DOM, captura e logs em `outputs/quantitative-filters-20261104/`.
**ACT-02 permanece parcial.** Cobertura factual nacional/regional, séries comparáveis, oferta laboral e metodologia quantitativa de potencial económico continuam pendentes conforme fontes aplicáveis. O cenário sintético não é estatística real. Conferência autenticada de Competitividade pela equipa e requisitos amplos V2-07/V2-10 preservados. Sem push, instalação deste delta, migração, importação, publicação automática, escrita activa, alteração de permissões ou reinício de serviços.
@@ -0,0 +1,62 @@
{
"date": "2026-11-04",
"scope": "main local; isolated PostgreSQL/PostGIS tests; no push or deployment",
"tested_base_commit": "66a5dbf3118de8ff4787d2a7a5854df578df01ba",
"qa": {
"host": "127.0.0.1",
"port": 55441,
"exclusive_cluster": "C:\\Users\\macbook\\.config\\investmoz\\main-qa-20261104-pgdata",
"databases": [
"investmoz_test",
"simulator_fiscal_delta_20261021_test",
"proactive_investment_20261027_test",
"main_reference_20261104_test",
"main_extra_20261104_test"
],
"ready": true
},
"tests": {
"unique_cases_passed": 1325,
"unique_assertions": 28377,
"failures_remaining": 0,
"feature_files_covered": 274,
"unit_cases": 94,
"schema_lifecycle_assertions": 3106,
"competitiveness": {
"tests": 7,
"assertions": 93
},
"province_filters": {
"tests": 4,
"assertions": 364
}
},
"initial_diagnostics_preserved": [
"snapshot encryption mismatch: verified original key used only in process APP_PREVIOUS_KEYS",
"combined RefreshDatabase traits regrouped; aborted reference copy restored",
"fiscal test fixtures separated from snapshot rules",
"GD/WebP loaded for QA CLI processes only",
"outdated audit total corrected: preserve13 business events and verify8 scan events and idempotent replay"
],
"application_source_changed": false,
"test_correction": {
"file": "tests/Feature/PortalServicesTest.php",
"sha256": "cc2e90c1929b6b2ef050c1c9e58ae2e15ef3c3189459f12ee25d24e26ce9651b",
"reason": "Preserve 13 business audit records and verify the eight existing malware worker start/result events and replay idempotency"
},
"preservation": {
"historical_files_verified_and_restored": 523,
"generated_outputs_archived": 17,
"original_qa_dumps_unchanged": true,
"matrix": {
"unique_requirements": 3555,
"columns": 49
},
"active_database_operations": 0,
"remote_push": false,
"active_migrations": false,
"service_restarts": false
},
"evidence": "outputs/main-db-qa-20261104/result.json; readiness.json; evidence-preservation.json; *.junit.xml",
"visual_acceptance": "previous pending states preserved; no visual conference performed"
}
@@ -0,0 +1,138 @@
{
"date": "2026-11-04",
"code_base": "dc0bbd74",
"scope": "main local; QA isolada; sem push ou instalação remota",
"target": {
"http": "http://127.0.0.2:18644",
"database": "main_visual_20261104_test",
"postgres": "127.0.0.1:55441"
},
"competitiveness": {
"authentication": "Fortify normal com MFA; actor sintético; sessão terminada no final",
"saved_and_reopened_version": 2,
"approved_version": 3,
"later_saved_draft_version": 4,
"anonymous_public_version": 3,
"draft_marker_absent_public": true,
"history_versions": [
4,
3,
2,
1
]
},
"province": {
"existing_results": 5,
"synthetic_lower_level_fixtures": 4,
"province_results": 9,
"district_results": 3,
"return_to_whole_province_results": 9,
"district_post_locality_filters_empty": true,
"confirmed_coordinates": 0,
"records_without_coordinates": 9,
"existing_rows_sha256": "6b6654b620c90920927f075d2cd0d89203997cc4971de2dee4053f3872822995",
"existing_rows_unchanged": true,
"coordinates_created": false
},
"csv": {
"before": "Província vazia apesar de localização/contexto disponível",
"after": "Província derivada da apresentação existente; coluna Localização e âmbito acrescentada após as oito colunas anteriores; contexto de corredor explicitamente qualificado",
"rows_verified": 9,
"columns": [
"Referência",
"Título",
"Sector",
"Província",
"Montante",
"Moeda",
"Maturidade",
"URL",
"Localização e âmbito"
],
"formula_escape_preserved": true,
"territorial_records_written": false
},
"delta_checks": {
"ProvinceSearchCompatibilityTest": {
"tests": 4,
"assertions": 399,
"passed": true
},
"InvestmentTerritoryTest": {
"tests": 13,
"assertions": 210,
"passed": true
},
"total_tests": 17,
"total_assertions": 609,
"phpstan_errors": 0,
"pint": "passed"
},
"previous_proofs": {
"receipt": "docs/investmoz/unification/main-local-database-qa-20261104.json",
"tests": 1325,
"assertions": 28377,
"preserved_not_reexecuted": true
},
"environment_observations": [
"Edge disponível nesta execução; sem reparação da extensão ou diagnóstico retroactivo de portas.",
"Servidor QA inicial apresentou erro RecursiveIterator; substituído apenas o processo HTTP próprio por PHP Laragon ZTS, após conferir endereço e router; configurações globais e serviços activos preservados."
],
"limitations": [
"Falta limite administrativo acessível desta província na QA; interface informa ausência, sem geometria inventada.",
"Não constitui conferência no portal remoto nem aceitação da equipa em dados reais.",
"ACT-20261029-02, V2-07 e V2-10 mantêm pendências transversais; nenhum encerramento global."
],
"active_database_changed": false,
"remote_push": false,
"proofs": {
"outputs/main-visual-qa-20261104/competitiveness-reopened-v2.jpg": {
"sha256": "600be345402f49be18ae2a0d711d1b4b9f22b92888cc99c85bef447748ff1e0b",
"bytes": 117130
},
"outputs/main-visual-qa-20261104/competitiveness-history-v4.jpg": {
"sha256": "c5bc80fa149a47acb4c611b556f11056b65601cccbbeb894dea67d25a003162f",
"bytes": 142439
},
"outputs/main-visual-qa-20261104/competitiveness-approved-anonymous-v3.jpg": {
"sha256": "f7f36c9a219fb985015076819876c8f0ef7b1106528c6831b0c37a0b37726744",
"bytes": 101483
},
"outputs/main-visual-qa-20261104/province-maputo-baseline-five.jpg": {
"sha256": "80c9796fbe3029e810276d2663b71af33a56773662ce580d9e4797df37c07f5b",
"bytes": 112558
},
"outputs/main-visual-qa-20261104/province-list-nine.jpg": {
"sha256": "45c587fa001878faf0086a9184b0ecc6b10de4ad49ad3bf6d4c46f6033eaa3f1",
"bytes": 119640
},
"outputs/main-visual-qa-20261104/province-map-nine-unlocated.jpg": {
"sha256": "f792ef6b6b7f4676f5ae3d03e5df0d14aadcf2b3f1d6c9091a2d69475f89d9f4",
"bytes": 127224
},
"outputs/main-visual-qa-20261104/district-three.dom.txt": {
"sha256": "327ef9df57857f6eec6a7394cfdd182e8b0831783a8dc18973af6d3e466bf80d",
"bytes": 15267
},
"outputs/main-visual-qa-20261104/province-map-nine.dom.txt": {
"sha256": "be98f4dcf36ffbe2aca791942f9d4399c91972a06cf6556d1bcf814a0cfec933",
"bytes": 23379
},
"outputs/main-visual-qa-20261104/competitiveness-anonymous-v3.dom.txt": {
"sha256": "a2bb6274cebb75de62976afb72d1235c32db2ba9572c37c0f6e988d5114b8aec",
"bytes": 10379
},
"outputs/main-visual-qa-20261104/province-maputo-nine.csv": {
"sha256": "f1c2beb7c2b49d7aba481cb612535fe6c4974d1f7af8da34e4cd27ad9825d8c5",
"bytes": 1584
},
"outputs/main-visual-qa-20261104/province-maputo-nine-corrected.csv": {
"sha256": "70fb2a79277f4d7bce523e18bf2e32299c18029be0400f01b43132588d9a251c",
"bytes": 2640
},
"outputs/main-visual-qa-20261104/province-fixtures.json": {
"sha256": "7a04c79f87f2fb77595e601ba21fae598c62c4fd2cb7efa21673949c186c8ed0",
"bytes": 1426
}
}
}
@@ -0,0 +1,112 @@
{
"date": "2026-11-04",
"base_commit": "6dde36b5",
"requirement": "ACT-20261029-02; V2-07/V2-10",
"scope": "Incremento local de cobertura administrativa por nível inferior; actividade global parcial",
"implemented": [
"Consulta opcional include_children=1 no endpoint existente; catálogo activa a opção.",
"Apenas filhos do nível imediatamente inferior e contexto anterior; não inclui outros territórios ou netos.",
"Geometria seleccionada prioritária; até 100 geometrias e 500 referências inferiores; truncagem explícita.",
"Contagens de referências acessíveis e territórios representados distintas; ausência na resposta não significa inexistência.",
"Fonte/edição preservadas; subdivisão identificada sem atribuição de local de investimento.",
"Leitura pública, autorização interna, snapshots aprovados e hashes de divulgação existentes preservados."
],
"contract": {
"route": "GET /api/v1/investment/territory-geometries",
"optional_parameter": "include_children=1",
"default": "sem filhos; mantém âmbito anterior",
"new_area_field": "child_territory",
"new_context_field": "child_coverage; null sem opção",
"registered_count": "referências activas acessíveis do nível imediatamente inferior",
"represented_count": "territórios inferiores distintos com geometria autorizada nesta resposta; não número de polígonos"
},
"checks": {
"database_tests": 11,
"database_assertions": 125,
"new_database_tests": 5,
"access_regression_tests": 6,
"frontend_tests": 9,
"phpstan_errors": 0,
"types": "passed",
"pint": "passed",
"lint": "passed",
"qa_build": "passed; avisos de fonte opcional e dimensão de chunks"
},
"visual": {
"target": "http://127.0.0.2:18644",
"database": "main_visual_20261104_test",
"province": "Maputo Província",
"registered_children": 8,
"represented_children": 0,
"opportunities_preserved": 9,
"new_geometry_or_coordinates": false,
"status": "Resumo de ausência de cobertura conferido no Edge; apresentação de polígonos inferiores exercitada em testes sintéticos, não demonstrada visualmente neste snapshot sem limites públicos."
},
"previous_proofs": {
"visual_receipt": "docs/investmoz/unification/main-local-visual-qa-20261104.json",
"all_12_artifact_hashes_verified_unchanged": true,
"database_receipt": "docs/investmoz/unification/main-local-database-qa-20261104.json",
"accepted_tests_not_reexecuted": 1325
},
"initial_attempt": "Fixture nova sem país-pai recusada pela integridade territorial; fixture corrigida sem relaxar o trigger. Tentativa inicial preservada em map-subdivision-delta-initial.*.",
"pending": [
"Camadas factuais de recursos, infra-estruturas, mão-de-obra e potencial económico permanecem por completar com dados compatíveis.",
"Cobertura administrativa depende das geometrias autorizadas existentes; não houve importação/publicação.",
"Conferência no portal remoto e aceitação da equipa mantêm-se pendentes.",
"ACT-02 e requisitos transversais não encerrados."
],
"safety": {
"branch": "main local",
"push": false,
"active_db_changed": false,
"migration": false,
"permissions_changed": false,
"other_services_restarted": false
},
"proofs": {
"outputs/map-subdivision-20261104/coverage-maputo.jpg": {
"sha256": "cddce8a1e2c4aa789aa1d4412c020940a6a1c7de9fe18467576f1449d3a7b476",
"bytes": 148244
},
"outputs/map-subdivision-20261104/coverage-maputo.dom.txt": {
"sha256": "81285b9ffd6c4db0d43f9d8126d6c91bdc86821b5ac9f64a20696cb9ceaa9135",
"bytes": 23142
},
"outputs/main-db-qa-20261104/map-subdivision-delta.log": {
"sha256": "affeadfee17b9402e5ecc75526a00b02d5109597d7a5e008dc99555ce2e031b8",
"bytes": 94
},
"outputs/main-db-qa-20261104/map-subdivision-delta.junit.xml": {
"sha256": "13bda85376995be9c018ca63f1f2420ba86d146be4ec024b8531e7e5ad5f4589",
"bytes": 2455
},
"outputs/main-db-qa-20261104/map-access-regression.log": {
"sha256": "0745715415ee74ca4fc27f1aec6ddd1dba32bbf67c54df59850ae2ecf2be1305",
"bytes": 95
},
"outputs/main-db-qa-20261104/map-access-regression.junit.xml": {
"sha256": "287e24afb3fc545ecff3d3ab63345ac700619d71c8819a3ec028a445d394136c",
"bytes": 2870
},
"outputs/main-db-qa-20261104/map-subdivision-frontend.log": {
"sha256": "fe3eb8a09e68e841ce44006477c3ad84f80ba5aa7a2f36b9e22c342bb5c7071e",
"bytes": 1335
},
"outputs/main-db-qa-20261104/map-subdivision-phpstan.log": {
"sha256": "1997495ca8f04c2ec09798d55607d1a2f85e3338b772f258666465bf658032d2",
"bytes": 49
},
"outputs/main-db-qa-20261104/map-subdivision-types.log": {
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"bytes": 0
},
"outputs/main-db-qa-20261104/map-subdivision-lint.log": {
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"bytes": 0
},
"outputs/main-db-qa-20261104/map-subdivision-build.log": {
"sha256": "81990a37221e58bb43b7718b22d824922c5db2e1ffd1c979d2d60002cf0f38e4",
"bytes": 31606
}
}
}
@@ -0,0 +1,78 @@
{
"date": "2026-11-04",
"requirement": "ACT-20261029-02",
"base_commit": "3f50f6663318bc084b4a233092c87c48e42df8e4",
"branch": "main",
"scope": "Filtros locais por indicador e sobreposicao inclusiva do periodo de referencia das observacoes quantitativas publicas",
"source": "quantitative_context existente, sem alteracao de API/schema/dados/permissoes",
"prior_proofs_verified_unchanged": 63,
"accepted_suite_preserved": "1325 testes / 28377 assercoes, sem repeticao integral",
"checks": {
"frontend_tests": 12,
"frontend_failures": 0,
"types": "passed",
"lint": "passed",
"qa_build": "passed",
"visual": "passed"
},
"visual": {
"url": "http://127.0.0.2:18644/oportunidades?province_id=bfde2651-2b46-5d9c-a500-733fa55a8c5c&view=map",
"database": "main_visual_20261104_test",
"synthetic_observations_reused": 5,
"fixtures_created": 0,
"production_inclusive_end": "2025-12-31",
"filtered_rows": 1,
"exact_value": "125.7500",
"inverted_period_alert": true,
"clear_restores_rows": 5,
"opportunities_unchanged": 12,
"date_input_update_verified": true
},
"limits": [
"Filtragem apenas do conjunto recebido; limites e truncagem preservados",
"Periodos incompletos, invalidos ou invertidos excluidos apenas quando existe filtro temporal",
"Nao usa data da fonte para inferir periodo; nao agrega nem converte grandezas",
"Cobertura factual, ACT-02 parcial e aceitacao da equipa mantidas",
"V2-07/V2-10 e conferencia autenticada de Competitividade preservadas"
],
"remote_push": false,
"active_writes": 0,
"artifacts": [
{
"path": "C:\\laragon\\www\\portal-investidor\\outputs\\quantitative-filters-20261104\\build.log",
"sha256": "8AA5789FFCD47993A524AC56D03D9BB467DFFD115741482557CC95641C946D18"
},
{
"path": "C:\\laragon\\www\\portal-investidor\\outputs\\quantitative-filters-20261104\\cleared.dom.txt",
"sha256": "BB20747D18A3AEB60871A1C7E48324D87D2B90C2A49EA371DD51003A94CFD3B2"
},
{
"path": "C:\\laragon\\www\\portal-investidor\\outputs\\quantitative-filters-20261104\\filtered.dom.txt",
"sha256": "15694ABA461F5AEE46B94CEEF2442A81150F12732B309060FFD48D4758DCEFF0"
},
{
"path": "C:\\laragon\\www\\portal-investidor\\outputs\\quantitative-filters-20261104\\filtered.jpg",
"sha256": "68AE971C375FC2B056289E90781C4D78640583C5BBBB1B1DA95FE170C7A25FC8"
},
{
"path": "C:\\laragon\\www\\portal-investidor\\outputs\\quantitative-filters-20261104\\invalid-period.dom.txt",
"sha256": "00CA11714D99D74949F6BE1FB4BD71BE1B78AE2FB9DFC1377E654C04C1A21013"
},
{
"path": "C:\\laragon\\www\\portal-investidor\\outputs\\quantitative-filters-20261104\\lint.log",
"sha256": "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855"
},
{
"path": "C:\\laragon\\www\\portal-investidor\\outputs\\quantitative-filters-20261104\\proofs-before.json",
"sha256": "1F7E40C076AD54E1C0AFD0711F69BD7FF13EA94A14532E791D8197A82A5D2059"
},
{
"path": "C:\\laragon\\www\\portal-investidor\\outputs\\quantitative-filters-20261104\\tests.log",
"sha256": "FB22D38E948C28DF960507F125B21E8BF60769CDCA045CCC540C4EB093C8FD7B"
},
{
"path": "C:\\laragon\\www\\portal-investidor\\outputs\\quantitative-filters-20261104\\types.log",
"sha256": "7505365A9FABA8ECBC6A5EEE8DEDFB464F91D43AFBC00DF22E79FF835F75C612"
}
]
}
@@ -0,0 +1,183 @@
{
"date": "2026-11-04",
"base_commit": "a9241ff2",
"requirement": "ACT-20261029-02; V2-07/V2-10",
"scope": "Primeiro recorte quantitativo: observações públicas existentes das zonas/parques do contexto do mapa; main local",
"inventory_before_fixtures": {
"database": "main_visual_20261104_test",
"visible_zones": 8,
"public_observations": [],
"no_private_ciphertext_read": true
},
"source_reused": "InvestmentZoneOperationalDetails::publicData -> integral.observations; mesma selecção de divulgação da ficha, sem desencriptar preparação privada",
"indicators": {
"workforce": [
"employment"
],
"economic_potential": [
"investment",
"production",
"exports",
"local_procurement"
]
},
"contract": {
"route": "GET /api/v1/investment/map",
"new_field": "quantitative_context",
"states": [
"available",
"unavailable",
"not_included"
],
"client_network_error_state": "error",
"zone_limit": 30,
"record_limit": 200,
"truncation_explicit": true,
"scope": "Mesmo conjunto de zonas autorizado pelos filtros existentes de contexto; não alarga pesquisa nem representa valores das oportunidades. Filtros específicos que já excluem zonas devolvem not_included.",
"metadata": [
"id",
"label",
"description",
"value",
"unit",
"period_start",
"period_end",
"scope",
"population",
"methodology",
"source_reference",
"source_date",
"knowledge",
"zone_id",
"zone_name",
"zone_version",
"url",
"theme",
"missing_metadata",
"phase_id",
"plot_id"
]
},
"behaviour": [
"Valores numéricos conhecidos preservados como texto, incluindo zero e precisão decimal; sem conversão binária para somas.",
"Valor desconhecido permanece null/Por determinar, mesmo perante quantidade contraditória numa fonte legada. Divergência conserva etiqueta.",
"Unidade, período, base/população, âmbito, metodologia e fonte/data em cada linha; metadados em falta explicitados, sem os completar por inferência.",
"Sem agregações, médias, câmbio, normalização ou comparação automática; emprego não equivale a disponibilidade laboral nem os montantes a índice de potencial.",
"Contagens de oportunidades e de observações territoriais separadas. Sem geometrias ou posições criadas.",
"Carregamento, erro, cadastro indisponível e âmbito não consultado distintos de resposta sem observações.",
"Números JSON com casas decimais extraídos como texto decimal pelo PostgreSQL a partir do mesmo snapshot carregado; metadados/valor/versão não são emparelhados com uma leitura posterior."
],
"checks": {
"database_tests": 11,
"database_assertions": 99,
"new_database_tests": 6,
"existing_thematic_regression_tests": 5,
"frontend_tests": 18,
"new_frontend_tests": 3,
"phpstan_errors": 0,
"types": "passed",
"pint": "passed",
"lint": "passed",
"qa_build": "passed; avisos de fonte opcional e dimensão de chunks"
},
"initial_attempts": "Nove dos dez casos BD passaram inicialmente; comparação da serialização JSON antes/depois do jsonb corrigida para comparar duas leituras reais do servidor. Fixture frontend repetia o rótulo de emprego numa linha de produção; rótulo corrigido. Sem alterar regras do produto para satisfazer estes testes. Logs BD iniciais conservados em quantitative-map-initial.*.",
"visual": {
"url": "http://127.0.0.2:18644/oportunidades?q=SYNTHETIC-QUANT-20261104&province_id=bfde2651-2b46-5d9c-a500-733fa55a8c5c&view=map",
"database": "main_visual_20261104_test",
"synthetic_zone_id": "b3349058-f120-4704-b34c-32ba7c1146c4",
"synthetic_observations": 5,
"workforce_rows": 2,
"economic_rows": 3,
"values_checked": [
"0 pessoas (emprego previsto sintético)",
"Por determinar (emprego indirecto sintético)",
"125.7500 toneladas",
"100000.00 MZN",
"25.50 USD em divergência"
],
"coordinates_created": false,
"geometry_created": false,
"existing_zones_sha256": "8d7d6d778f826453556caa131def70747706ac338aac472959a2057811d4cc77"
},
"preservation": {
"previous_artifact_hashes_verified_unchanged": 51,
"accepted_full_database_tests_preserved_not_reexecuted": 1325,
"previous_pending_items_preserved": true
},
"pending": [
"O snapshot QA tinha oito fichas públicas e zero observações quantitativas divulgadas; demonstração não estabelece cobertura factual real nem diagnostica dados do remoto.",
"Séries oficiais/regionais comparáveis, oferta laboral, natureza prevista/efectiva e metodologia de potencial quantitativo dependem dos dados aplicáveis, sem novos índices presumidos.",
"Cobertura nacional, georreferenciação própria, conferência remota e aceitação da equipa continuam pendentes. ACT-02/V2-07/V2-10 não encerrados."
],
"safety": {
"branch": "main local",
"push": false,
"active_database_changed": false,
"migration": false,
"permissions_changed": false,
"automatic_publication": false,
"service_restarts": false
},
"proofs": {
"outputs/quantitative-map-20261104/source-inventory-before-fixtures.json": {
"sha256": "b547bd8c34c09c7d779c8226e9a68ad7160d008bf9e2f68832c694022eb36597",
"bytes": 142
},
"outputs/quantitative-map-20261104/prior-evidence.json": {
"sha256": "49989437b12a348ca866404523e81d33fd695e0298acd2574ed579eb1872a915",
"bytes": 6766
},
"outputs/quantitative-map-20261104/fixture-receipt.json": {
"sha256": "2053ed900664fa65372bc4c0404da872b167213ca025ba88799af8221acfd46f",
"bytes": 409
},
"outputs/quantitative-map-20261104/all-indicators.jpg": {
"sha256": "9d1a455c1298f2c2579f37b354ab72aae32b5045c957a203b657cb6932d871da",
"bytes": 217406
},
"outputs/quantitative-map-20261104/all-indicators.dom.txt": {
"sha256": "c8e998ea7f4efdb1df6df57c07b014d86efdd4af9479ebea7bb52af964013285",
"bytes": 23575
},
"outputs/quantitative-map-20261104/workforce-indicators.jpg": {
"sha256": "e14ee34543d5482a5c288bd3bb0733b496d4aecefee12cd0f5d0e5d8aa6f1576",
"bytes": 187352
},
"outputs/quantitative-map-20261104/workforce-indicators.dom.txt": {
"sha256": "d659fd72bf7df061c7ff6542c0dee8e10ef74a4a7ba45ba1abb3adb0fc5a4d03",
"bytes": 17344
},
"outputs/quantitative-map-20261104/economic-indicators.dom.txt": {
"sha256": "11ebdf8284c36f20b2196f4bc4eada37c70c99d232c03a31ef54d673cfb8ddbb",
"bytes": 19366
},
"outputs/main-db-qa-20261104/quantitative-map.log": {
"sha256": "7914ad0e266ae90a884c5f3ffa783a7859d4e342a7a64fb6c96f90388be44f97",
"bytes": 96
},
"outputs/main-db-qa-20261104/quantitative-map.junit.xml": {
"sha256": "5d01befa688ec8edd9e3a58f9821f248fda5ec6c86318a02ada7b48c6b4b9494",
"bytes": 4703
},
"outputs/main-db-qa-20261104/quantitative-map-frontend.log": {
"sha256": "240847ad2e9d75e2d77bb3ea66e2814d18dbe7b85e79722da11306ec843b2941",
"bytes": 2476
},
"outputs/main-db-qa-20261104/quantitative-map-phpstan.log": {
"sha256": "1997495ca8f04c2ec09798d55607d1a2f85e3338b772f258666465bf658032d2",
"bytes": 49
},
"outputs/main-db-qa-20261104/quantitative-map-types.log": {
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"bytes": 0
},
"outputs/main-db-qa-20261104/quantitative-map-lint.log": {
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"bytes": 0
},
"outputs/main-db-qa-20261104/quantitative-map-build.log": {
"sha256": "6cefd15069e09c1bfd8b404842b719227ea7785e7908f07a69f66029fe4639e3",
"bytes": 31606
}
}
}
@@ -0,0 +1,151 @@
{
"date": "2026-11-04",
"base_commit": "bbc05f1d",
"requirement": "ACT-20261029-02; V2-07/V2-10",
"scope": "Camadas de evidência temática pública de recursos e infra-estruturas no catálogo existente; main local",
"inventory_before_fixtures": {
"database": "main_visual_20261104_test",
"published_opportunities": 48,
"public_proactive_summaries": 2,
"public_local_evidence": {
"resource": 0,
"infrastructure": 0
},
"methodology_evidence_not_mapped": 0,
"sources": [],
"other_existing_sources": {
"investment_zones.resources/source_evidence": "Textual scope; not converted to georeferenced resource availability",
"portal_resources/legacy_content": "Editorial and document sources; no automatic thematic geometry",
"private proactive evidence": "Not consulted or published by this increment",
"administrative boundaries": "Existing separate layer retained"
}
},
"reuse": [
"ProactiveInvestment::publicData: síntese já publicada/seleccionada, sem ler rascunho privado para divulgação.",
"Categorias resource/infrastructure com propósito local; metodologia e outras categorias excluídas.",
"InvestmentCatalogue::query e map: mesmos filtros, geometrias públicas e limite de 1000 oportunidades.",
"Mapa único e contexto administrativo do incremento bbc05f1d preservados."
],
"contract": {
"route": "GET /api/v1/investment/map",
"new_field": "thematic_records; id/title/url/location/state/limitations/evidence",
"evidence_fields": [
"id",
"category",
"statement",
"source",
"locator",
"period",
"scope",
"coverage",
"verified_on"
],
"area_field": "opportunity_id para filtrar áreas da mesma oportunidade sem atribuir geometria à evidência"
},
"behaviour": [
"Todas as oportunidades conserva os pontos/áreas anteriores.",
"Seleccionar uma categoria apresenta apenas locais/áreas das oportunidades com evidência temática pública local; sem criar posições.",
"Fichas sem ponto continuam consultáveis com as evidências e fontes; ausência não equivale a inexistência ou zero.",
"Escolha da camada recuperada na sessão do navegador; falha/valor inválido de armazenamento não bloqueia mapa.",
"Contagens gerais da pesquisa distinguem-se do subconjunto da camada; CSV e resultados gerais mantêm filtros anteriores.",
"Sem inferir mão-de-obra ou potencial económico de descrições ou contagens."
],
"checks": {
"database_tests": 8,
"database_assertions": 438,
"new_database_tests": 4,
"existing_province_regression_tests": 4,
"frontend_tests": 14,
"new_frontend_tests": 5,
"phpstan_errors": 0,
"types": "passed",
"pint": "passed",
"lint": "passed",
"qa_build": "passed; avisos de fonte opcional e dimensão de chunks"
},
"visual": {
"target": "http://127.0.0.2:18644",
"database": "main_visual_20261104_test",
"data": "Duas fixtures exclusivamente sintéticas; base QA sem evidência temática local publicada antes das fixtures.",
"resource_selected": true,
"infrastructure_selected": true,
"infrastructure_selection_recovered_after_reload": true,
"source_period_scope_limits_visible": true,
"points_created": false,
"geometry_created": false,
"existing_opportunities_unchanged_sha256": "c9d156f8a71d9196b4f36c3558b830267232b2d407c7abcf9b92bc027bce99d3"
},
"preservation": {
"prior_artifacts_verified_unchanged": 23,
"accepted_database_suite_tests_preserved_not_reexecuted": 1325,
"prior_records_and_pending_items_retained": true
},
"pending": [
"Cobertura factual real de recursos e infra-estruturas depende de evidências públicas locais existentes no destino; nada foi publicado automaticamente.",
"Posição exacta, capacidade e disponibilidade dos recursos/infra-estruturas não foram deduzidas da posição da oportunidade.",
"Mão-de-obra, potencial económico quantitativo, restantes fontes temáticas e cobertura cartográfica continuam pendentes.",
"Sem conferência remota ou encerramento de ACT-02/V2-07/V2-10."
],
"safety": {
"branch": "main",
"push": false,
"active_database_changed": false,
"schema_or_permissions_changed": false,
"automatic_publication": false,
"other_services_restarted": false
},
"proofs": {
"outputs/thematic-map-20261104/source-inventory-before-fixtures.json": {
"sha256": "7366d737a5cd516c9667a43d62909d1529928285f4da7197c8f0bddab7f85503",
"bytes": 700
},
"outputs/thematic-map-20261104/fixture-receipt.json": {
"sha256": "dcf66ccf5773a694638e86cde14bc40e7d2fec56867d6146ac8c9077fc03659d",
"bytes": 417
},
"outputs/thematic-map-20261104/resources.jpg": {
"sha256": "f7c3d03e09728d320662a648b083b96ad12bc5bc40825f22529c2d9c882c164f",
"bytes": 162214
},
"outputs/thematic-map-20261104/resources.dom.txt": {
"sha256": "6816332137f2fc2a0c840e213997e9f64589166e3ce0e6ebd1619b27bbde5de9",
"bytes": 14232
},
"outputs/thematic-map-20261104/infrastructure-reopened.jpg": {
"sha256": "f97455f86832e72eaffd04ce01f53d0a339b966d658c68ba62cfc1d5eb4bc5ba",
"bytes": 153964
},
"outputs/thematic-map-20261104/infrastructure-reopened.dom.txt": {
"sha256": "c5fc7b92df9a69c1026a0f7e61217ce96ac5f27b6e3b15d56a2a53a3c3ffb15f",
"bytes": 14254
},
"outputs/main-db-qa-20261104/thematic-map-delta.log": {
"sha256": "ae49afa4a4f8af69ad3ae88470da9d556322b8b6b57b035e2e6476a24d287b1a",
"bytes": 96
},
"outputs/main-db-qa-20261104/thematic-map-delta.junit.xml": {
"sha256": "cc3e177001c7c254832c4eebfa8e13cf5b9a3067121f1fb1efe517fe4d4b8d6e",
"bytes": 3756
},
"outputs/main-db-qa-20261104/thematic-map-frontend.log": {
"sha256": "5523fa5f62f0d40b0ada1ea0faf9d90e649c797dd1b6df89b4fa7086b7e4a6bd",
"bytes": 1970
},
"outputs/main-db-qa-20261104/thematic-map-phpstan.log": {
"sha256": "1997495ca8f04c2ec09798d55607d1a2f85e3338b772f258666465bf658032d2",
"bytes": 49
},
"outputs/main-db-qa-20261104/thematic-map-types.log": {
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"bytes": 0
},
"outputs/main-db-qa-20261104/thematic-map-lint.log": {
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"bytes": 0
},
"outputs/main-db-qa-20261104/thematic-map-build.log": {
"sha256": "2f452020e952def0eb1fd9c4469880f35016b23d3757054d84520a5d5d4ef1a1",
"bytes": 31606
}
}
}
@@ -1,5 +1,455 @@
## Energia — consulta e preparação no mesmo dossier (QA,11/11/2026)
1. Abra **Sectores de investimento → Energia** (`/sectores/energia-especifica`). Seleccione localização e segmento energético,ou pesquise uma actividade.
2. Consulte a área de cadeia de valor e a respectiva evidência. Os projectos de referência,contexto de financiamento,quadro sectorial,riscos e fontes ficam em secções expansíveis. Os valores e calendários conservam as distinções das fontes.
3. Escolha **Preparar uma ideia nesta cadeia de valor**. Depois de autenticar,confira o resumo e confirme no dossier existente,ou crie um. Área,segmento,localização e versão consultada acompanham a escolha.
4. Reabra o dossier e consulte **Sectores e cadeias de valor**. Pode acrescentar uma referência deMineração no mesmo dossier;nenhuma substitui a outra. Ambas ficam disponíveis como evidências para a análiseSWOT,sem conclusões automáticas.
Localização não discriminada permanece por definir;uma bacia ou corredor não é convertido numa província. Referências de produção,capacidade,investimento,estudo ou calendário não significam oferta aberta,financiamento garantido ou licença. A incorporação não activa incentivos fiscais ou novos direitos.
**Estado:** os dois sectores estão implementados emQA e agrupados para entrega;instalação e conferência visual ainda pendentes. Referência da homologação6d3bf293,com270instituições preservadas. Prova conjunta `outputs/sector-resource-20261111/verification.json`.
## Mineração — consulta e preparação no mesmo dossier (QA,11/11/2026)
1. Abra **Sectores de investimento → Mineração** (`/sectores/mineracao`). Escolha localização e grupo mineral,ou pesquise uma actividade. Pode consultar recursos,projectos,indicadores por período e fontes sem autenticação.
2. Em **Possibilidades na cadeia de valor**,consulte a actividade e expanda **Evidência e âmbito territorial**. Seleccione **Preparar uma ideia nesta cadeia de valor**.
3. Autentique-se apenas para guardar. Confira o resumo e escolha **Continuar neste dossier com o setor**,ou crie um dossier. A área,grupo mineral,localização e versão consultada acompanham a escolha; nenhum dossier é escolhido automaticamente.
4. Ao reabrir o dossier,consulte **Sectores e cadeias de valor** e volte à referência pelo respectivo acesso. Pode acrescentar outra área sem substituir a anterior. As referências ficam disponíveis na preparação/análise SWOT como evidências; conclusões são elaboradas e revistas pela equipa.
As ocorrências servem a preparação do investimento. Não atribuem concessão,licença,promotor ou disponibilidade. “Maputo” pode precisar de distinção entre província e cidade; não foram criadas coordenadas ou associações territoriais por aproximação. Produção,recursos,capacidade e previsões são apresentados separadamente com os períodos da fonte. A escolha mantém rastreabilidade privada no dossier,sem publicar a análise ou enviar dados a terceiros.
**Saldo:** percurso implementado/testado em QA; instalação e revisão visual pendentes. A referência instalada continua6d3bf293;270instituições e restantes funcionalidades preservadas. Registo técnico no mesmo backlog/matriz e `outputs/mining-reference-20261111/verification.json`.
## 11/11/2026 — perfis uniformes e menu de seis grupos instalados em6d3bf293
Instalação expressamente autorizada deeca67191 concluída na revisão integrada `6d3bf2937ee6f5a58267967516560ba73d2cc936`,conferida às12:38:49Africa/Johannesburg(10:38:49UTC). A guarda inicial detectoua400802b já instalada e parou antes de escrever. Comparação delimitada identificou seis alterações independentes de tradução/cabeçalho fixo,integradas e preservadas;os quinze ficheiros aprovados deeca67191 permaneceram idênticos. Pacote reconciliado aplica/reverte exactamente sobrea400802b;pacote original preservado. Não foi necessária nova decisão de âmbito.
Utilizável em `/fundos` e nos detalhes deQuorus/Moza:mesmo componente público,descrição e produtos uma vez,categoria/tipo numa linha,contactos e moradas conhecidos sem duplicação;sem secções vazias,avisos genéricos,preços antigos ou notas internas. Menu principal **Início–Descoberta–Realização–Suporte–Sobre nós–Mais**,com concursos emDescoberta e acessos de trabalho preservados emMais. Fontes,históricos,contactos e moradas originais continuam no editor;nenhuma deduplicação de dados foi executada.
Backup novo/restauro isolado conferidos:190tabelas/369ficheiros. Vinte e um ficheiros por hash(15aprovados+6preservados);revisão,entrada,up,login,Concursos,fundos e simuladorHTTP200. Configuração intacta,zero migrações/importações/republicações. Totais/checksums idênticos ao backup nas sete tabelas protegidas:funding_programmes,funding_programme_versions,funding_programme_interests,desk_cases,simulation_form_drafts,labour_demands,training_capacities.275registos/270instituições publicadas preservados. Recibo `outputs/funding-uniform-menu-20261111/installation.json` e reconciliação no mesmo directório. Pacote `outputs/entrega-financeiras-menu-reconciliada-20261111.zip`,SHA256 `dc0be0d20569a96a3338ba140b8430013f146deb6eafd73b66343c2841d91e16`;reversão paraa400802b,preservando trabalho alheio.
Este conjunto deixa de ser pendência de implementação/instalação;conferência visual permanece separada. Não requer seleccionar/publicar novamente instituições. Restante saldo de módulos e fiscal conservado;portal não declarado integralmente concluído.
## 11/11/2026 — uniformização Quorus/Moza e menu de seis grupos em QA
Correcção orientada pelos exemplos expressos do proprietário:lista e detalhe reutilizam agora **o mesmo componente público**. Descrição única,categoria/tipo numa linha,produtos/serviços uma vez,contactos e moradas apenas quando disponíveis. Quorus:descrição de corretagem eSeguros uma vez;sem secções vazias ou avisos genéricos. Moza:descrição paraPME,CréditoPME/instrumentos uma vez,umtelefone e umamorada;sem notas de publicação,preços antigos,comissões/garantias por reconfirmar,entidade responsável repetida,sectores/cobertura desconhecidos ou referências institucionais duplicadas. Não inventa dados nem altera condições guardadas. Ligações dos produtos/contactos permanecem acessíveis uma vez;moradas diferentes são preservadas.
Menu principal exactamente **Início–Descoberta–Realização–Suporte–Sobre nós–Mais**. Concursos movido paraDescoberta;Biblioteca eInstituições financeiras com os nomes pedidos;conteúdos deSobre nós/Mais preservados. Acesso de trabalho deslocado paraMais,sem perder permissões;conta/contexto/licenciamentoS08 e filtros preservados. Instruções permanentes actualizadas com o padrão pedido.
**QA:**33verificações dirigidas aprovadas,com três casos novos:exemplos públicos exactos nas duas páginas,deduplicação conservadora e cabeçalho real com seis entradas em quatro combinações(público/autenticado,móvel/computador). Reutilizadas provas dos fluxos de interesse/dossier/editorial e das permissões,não reexecutadas na base. Fontes de270instituições intactas,sem migração/importação/republicação. Artefactos `outputs/funding-uniform-menu-20261111/verification.json` eHTMLs reais emSSR. Não é conferência visual de navegador;tipos/lint/compilação aprovados;pacote Git com aplicação/reversão conferidas no fecho.
**Estado:** implementado em QA,não instalado;homologação mantém `a59142ca` e270instituições publicadas. Restam instalação e conferência visual;não requer seleccionar/publicar novamente instituições. Restante saldo de módulos e fiscal preservado. Próximo trabalho:entregar este conjunto compatível e continuar os resíduos de implementação acordados.
## 11/11/2026 — apresentação financeira profissional instalada ema59142ca
Instalação expressamente autorizada,executada pelo processo Git existente e conferida às11:38:54Africa/Johannesburg(09:38:54UTC): `a59142caa2fc277975be82fb550223ff7d949594`. O directório `/fundos` e o detalhe apresentam descrição e produtos/serviços conhecidos,sem usar texto de importação,fichas ou documento gerador como apresentação. Descrições específicas são preservadas;resumo genérico exacto passa a uma descrição pela natureza publicada. Não são inventadas ofertas para instituições sem produtos específicos identificados. Metadados/fontes/histórico mantidos no editor;ligações institucionais e datas úteis acessíveis separadamente. Orientação transversal registada emAGENTS.
Backup novo/restauro isolado conferidos:190tabelas/369ficheiros. Dezasseis fontes por hash;revisão,entrada,up,login,Concursos,fundos e simuladorHTTP200. Configuração intacta,zero migrações/importações/republicações do lote. Totais/checksums idênticos ao backup nas sete tabelas protegidas:funding_programmes,funding_programme_versions,funding_programme_interests,desk_cases,simulation_form_drafts,labour_demands,training_capacities.275registos/270instituições publicadas preservados. Recibo `outputs/funding-public-editorial-20261111/installation.json`;pacote `outputs/entrega-financeiras-editorial-20261111.zip`,SHA256 `531db038a46ee32606ebbed0aab43630e2f68b05569ed3a6977b85b59ea68b8b`. Revisão8f295f05 preservada para reversão.
Este delta deixa de ser pendência de implementação/instalação. Visual permanece separado,enriquecimento de produtos específicos depende de informação disponível. Não é necessário seleccionar/publicar novamente as270instituições. Restantes módulos e resíduos fiscais conservam o saldo existente;portal não declarado integralmente concluído.
## 11/11/2026 — apresentação pública profissional do directório financeiro em QA
**Pedido do proprietário:** descrições profissionais e apelativas,com descrição/produtos/serviços úteis ao investidor,sem texto de fichas,importação ou documento gerador;orientação transversal registada emAGENTS. O directório e o detalhe passam a uma apresentação editorial comum:reconhece exclusivamente o resumo genérico exacto da importação e usa a natureza publicada da entidade;descrições editoriais específicas são preservadas e limpas da fórmula genérica final. Produtos identificados são apresentados pelos nomes/instrumentos existentes;os sete catálogos específicos disponíveis não são extrapolados para outras entidades. Nas restantes,apresentam-se áreas de serviço conhecidas ou contacto para conhecer soluções,sem inventar produtos/condições.
Retirados da apresentação pública nomes de ficheiros,hashes,linhas de importação,códigos de sucessora e descrições de registo interno. Ligações institucionais e datas úteis continuam acessíveis;metadados e fontes permanecem intactos no cadastro/editor. Instituições históricas/fundidas continuam identificadas como tal. Instituições sem chamada não mostram uma secção vazia de candidatura. Textos da selecção guardada e chamadas públicas dePorque investir/Serviços ajustados à linguagem do investidor. Fluxos de interesse,dossier,edição,revisão/publicação e filtros preservados.
**QA:** nove verificações afectadas aprovadas,três novas incluindo270referências fornecidas,descrições específicas preservadas,produtos não inventados e renderização dos componentes públicos reais de lista/detalhe. Sem escrita em dados activos ou nova publicação do lote;é uma projecção editorial dos dados já publicados. Evidência `outputs/funding-public-editorial-20261111/verification.json` e quatroHTMLs;isto não constitui conferência visual no navegador. Tipos/lint/compilação aprovados;pacote Git com aplicação/reversão conferidas no fecho.
**Estado:** implementado em QA,não instalado;homologação mantém `8f295f05` e270instituições publicadas. Restam instalação/visual e enriquecimento de produtos específicos quando houver informação disponível. O saldo dos outros módulos e resíduos fiscais permanece inalterado. Não é necessário seleccionar ou publicar novamente as270instituições para esta alteração de apresentação.
## 11/11/2026 — rectificação do volume PEC instalada em8f295f05
Instalação expressamente autorizada,executada pelo processo Git existente e conferida às11:03:24Africa/Johannesburg(09:03:24UTC): `8f295f05510c786afeb8ea1dfa692e08d90a0d30`. No simulador fiscal,L8,**Rectificar volume na simulação PEC** conserva o valor original e o corrigido,com escolha explícita/fonte e efeito na estimativa apresentados no relatório. A hipótese de tesouraria anterior exige reconciliação se o total mudar;nenhum comprovativo pago,dedução ou saldo oficial é duplicado. Não executa pagamentos nem submete declaração rectificativa.
Backup novo/restauro isolado conferidos:190tabelas/369ficheiros. Quinze fontes por hash;revisão,entrada,up,login,Concursos,fundos e simuladorHTTP200. Configuração intacta,zero migrações/importações. Totais/checksums idênticos ao backup nas sete tabelas protegidas:funding_programmes,funding_programme_versions,funding_programme_interests,desk_cases,simulation_form_drafts,labour_demands,training_capacities.275fichas/270instituições publicadas preservadas. Recibo `outputs/irpc-pec-correction-20261111/installation.json`;pacote `outputs/entrega-pec-rectificacao-20261111.zip`,SHA256 `559e4477059b092a193d990f7dfb6cb50007d43c24ec00e2484326f076e0c973`. Revisãoa708f079 preservada para reversão.
Este recorte29(3) deixa de ser pendência de implementação/instalação. Visual permanece separado;correcções fora do recorte têm âmbito próprio. Restam última base29(5),PPC excedente,períodos/regimes especiais e demais resíduos L1–L7/restanteL8/L9–L16 e módulos. Próximo trabalho:primeiro residual de simulação executável com as fontes aceites,sem reabrir este recorte. Portal não declarado integralmente concluído.
## 11/11/2026 — rectificação do volume PEC na simulação em QA
**Recorte29(3) avançado:** fonte documental permite rectificar o volume de negócios2025 usado na estimativa PEC2026. Valor original,valor corrigido,titular,exercício e fundamento ficam conservados;escolha explícita entre aplicar a correcção e manter o original. Não submete declaração rectificativa. A estimativa recalcula0,5%,limites e PPC anteriores uma única vez;relatório mostra origem,valor corrigido e efeito na estimativa. Mudança da base/titular/exercício exige reconferência;fonte ou valor ausente não é zero.
Cálculo→API privada→recuperação→HTML/relatório do mesmo dossier em cinco cenários:aumento40.000→90.000MZN,redução40.000→20.000,original conservado40.000,base divergente e correcção para volumezero indeterminados. Hipótese de caixa anterior40.000 deixa de reconciliar quando o total muda;não é sobrescrita. Saldo anual138MZN,comprovativos pagos e histórico anterior preservados. QA55441 existente:uma prova nativa/47asserções com rollback,cinco testes novos de cálculo/interface e40afectados aprovados;tipos/lint/compilação aprovados. Provas `outputs/irpc-pec-correction-20261111/*-verification.json`;pacote Git com aplicação/reversão conferidas no fecho. Não é conferência visual.
**Estado:** implementado em QA,ainda não instalado;homologação mantém `a708f079` e270instituições publicadas. Fonte:RIRPC,Decreto9/2008,art29(3),BR16/I16-04-2008,original já aceite SHA256 `fc373ff5637edf75ec6a9595dec8763a8503d4ee786e3b7d8f1c742eae9d80ee`. Sem migração,base,tabela ou direito novo. Restam instalação/visual,última base29(5),PPC excedente,períodos/regimes especiais e demais resíduos fiscais L1–L7/restanteL8/L9–L16. Correcções fora deste recorte conservam âmbito próprio;L8 e portal não são declarados integralmente concluídos. Próximo trabalho:primeiro residual de simulação executável pelas fontes aceites,sem reabrir este recorte.
## 11/11/2026 — finalidade de viabilidade e projecção PEC instaladas ema708f079
Instalação expressamente autorizada,executada pelo processo Git existente e conferida às10:45:50Africa/Johannesburg(08:45:50UTC): `a708f079b318a13100644c27287f1caa854ecf1d`. No simulador fiscal,L8,os ecrãs apresentam **Simular PPC/PEC**,estimativas fiscais e hipóteses de tesouraria para avaliar a viabilidade do negócio. A hipótese PEC permite distribuir valores por três meses ou concentrar a estimativa num mês escolhido,com soma conferida e prazo legal não certificado. Rascunho,recuperação,histórico e relatórios preservam escolhas;dados documentais já pagos permanecem distintos. O portal não executa pagamentos,nem submete declarações fiscais.
Backup novo/restauro isolado conferidos:190tabelas/369ficheiros. Vinte e quatro fontes por hash;revisão,entrada,up,login,Concursos,fundos e simuladorHTTP200. Configuração intacta,zero migrações/importações. Totais/checksums idênticos ao backup nas sete tabelas protegidas:funding_programmes,funding_programme_versions,funding_programme_interests,desk_cases,simulation_form_drafts,labour_demands,training_capacities.275fichas/270instituições publicadas preservadas. Recibo `outputs/irpc-pec-plan-20261111/installation.json`;pacote `outputs/entrega-simulacao-viabilidade-20261111.zip`,SHA256 `a4dbbe26ca72230e3ff7a5c2a744c187a93380a4857e522b1db5ee17acce3ca5`. Revisãobcbbe51c preservada para reversão.
Este delta deixa de ser pendência de implementação/instalação. Conferência visual permanece separada. Execução de pagamentos não é critério de conclusão deste simulador,conforme clarificação expressa registada emAGENTS. Próximo trabalho:primeiro residual de simulação executável pelas fontes aceites. Base29(5),rectificação29(3),PPC excedente,períodos/regimes e restantes resíduos L1–L7/restanteL8/L9–L16 e módulos mantêm o saldo;portal não declarado integralmente concluído.
## 11/11/2026 — finalidade do simulador e projecção de tesouraria PEC
**Clarificação expressa do proprietário:** o portal simula impostos para avaliar a viabilidade do negócio; não faz pagamentos. A apresentação anterior de «obrigação» e «plano de pagamento» criava uma expectativa errada de execução. Ecrãs PPC/PEC passam a **Simular PPC/PEC**,estimativa fiscal e projecção de tesouraria. Fontes legais e dados documentais já pagos continuam a informar o cálculo; não são operações executadas pelo portal. A execução de pagamentos não é pendência deste simulador.
**Implementado em QA:** hipótese PEC distribuída por Junho/Agosto/Outubro com montantes explícitos cuja soma corresponde exactamente ao total,ou hipótese concentrada num mês escolhido. Não inventa igualdade/arredondamento ou validação de prazo para a opção única. Valores desconhecidos,divergência da soma,total alterado e ausência de estimativa positiva invalidam a projecção dependente,sem bloquear o cálculo fiscal. Trocar hipótese conserva edições;resumo com Alterar. Rascunho privado,recuperação,histórico e relatório do mesmo dossier reutilizados. As estimativas não alimentam os comprovativos pagos nem alteram deduções anuais.
Cinco verificações novas de cálculo/interface e35afectadas aprovadas;percurso sintéticoAPI/recuperação/relatório em quatro cenários(distribuído,concentrado,divergente,total alterado),com saldo anual138MZN conservado. Provas `outputs/irpc-pec-plan-20261111/*-verification.json`. Sem migração,base,papel ou permissão nova. Tipos/lint/compilação aprovados;uma prova nativa/38asserções com rollback. Pacote Git e reversão conferidos no fecho. Isto não é conferência visual.
**Estado:** QA,ainda sem instalação;homologação permanece `bcbbe51c` e270instituições publicadas. Restam instalação/visual e recortes fiscais de base29(5),rectificação29(3),PPC excedente,períodos/regimes e outras regras de simulação;prazo legal da hipótese única não é certificado. L1–L7/restanteL8/L9–L16 e demais módulos permanecem no saldo,sem transformar o portal em sistema de execução fiscal. Próximo trabalho:primeiro residual de simulação executável com fontes aceites.
## 11/11/2026 — total e calendário PEC instalados embcbbe51c
Instalação expressamente autorizada,executada pelo processo Git existente e conferida às10:28:18Africa/Johannesburg(08:28:18UTC): `bcbbe51c2fc279d5b2d85412f7bcb6ca2423a9b6`. Utilizável no simulador fiscal,fecho anual L8: **Preparar obrigação PEC**,com total ordinário2026 baseado no volume documental2025,limites antes dos PPC pagos anteriores,mesesJunho/Agosto/Outubro e dispensa no ano de início efectivo. Guardar/recuperar pelo rascunho privado e apresentar no relatório do mesmo dossier. Não cria pagamento,dedução anual ou reembolso.
Backup novo/restauro isolado conferidos:190tabelas/369ficheiros. Catorze fontes por hash;revisão,entrada,up,login,Concursos,fundos e simuladorHTTP200. Configuração preservada,zero migrações/importações. Sete tabelas protegidas mantêm totais/checksums do backup:funding_programmes,funding_programme_versions,funding_programme_interests,desk_cases,simulation_form_drafts,labour_demands,training_capacities.275fichas/270instituições publicadas preservadas. Recibo `outputs/irpc-pec-obligation-20261111/installation.json`;pacote preservado `outputs/entrega-pec-obrigacao-revisada-20261111.zip`,SHA256 `a9d9bb089305003487695969d653c7f876a913fce44bbbffb5191c1a792a9184`. Revisãofbff2660 preservada para reversão.
Este recorte deixa de ser pendência de implementação/instalação;visual permanece separado. Restam montantes por prestação/pagamento único,última base29(5),rectificação29(3),PPC excedente,períodos/regimes especiais,guias/juros. Próximo trabalho:primeiro desses casos executável pelas fontes aceites,sem repetir o recorte instalado. L1–L7/restanteL8/L9–L16 e restantes módulos continuam no saldo;portal não declarado integralmente concluído.
## 11/11/2026 — total e calendário PEC ordinário em QA
**Novo recorte L8:** preparação do total PEC2026 pelo volume de negócios documental2025:0,5%,limites30.000/100.000MZN aplicados antes de deduzir PPC efectivamente pagos no ano anterior. MesesJunho/Agosto/Outubro,sem inventar dia limite ou importar o arredondamento PPC. Dispensa do ano de início efectivo da actividade29(4) fundamentada separadamente. Fonte:RIRPC,Decreto9/2008,art29(1–5),BR16/I16-04-2008,original SHA256 `fc373ff5637edf75ec6a9595dec8763a8503d4ee786e3b7d8f1c742eae9d80ee`, já preservado no acervo.
Formulário progressivo reutiliza titular/exercício e conserva valores ao ocultar campos sem aplicação; rascunho privado e relatório do mesmo dossier reutilizados. Total preparado não é PEC pago,nem cria dedução anual,reembolso,guia ou consumo de saldo oficial. Ausência de informação não equivale a zero. Sem volume no ano anterior exige base29(5);PPC superiores ao montante limitado ficam por reconciliar,sem obrigação negativa. Esses casos não impedem o cálculo anual independente.
**Percurso sintético:** cálculo→API privadaPOST/PUT→GET→recálculo→HTML/relatório:40.000MZN ordinário,20.000mínimo antes de PPC,90.000máximo antes de PPC,dispensa inicial0,volumezero e desconhecido indeterminados. Histórico e saldo anual138MZN preservados. QA55441 existente,uma prova nativa/56asserções com rollback,sem nova base/tabela/porta/papel/migração. Seis verificações novas de cálculo/interface e29afectadas aprovadas; tipos/lint/compilação aprovados. Provas `outputs/irpc-pec-obligation-20261111/*-verification.json`;pacote Git com aplicação/reversão conferidas no fecho.
**Saldo:** implementado em QA,ainda não instalado. Homologação mantém `fbff2660` e270instituições publicadas. Restam entrega Git e visual;montantes por prestação/opção de pagamento único,última base29(5),rectificação29(3),PPC acima do limite,períodos/regimes especiais,guias/juros permanecem abertos. L8 não é integralmente concluído;resíduos L1–L7/restanteL8/L9–L16 e restantes módulos conservados. Próximo trabalho:primeiro desses casos executável com as fontes aceites,sem reabrir os recortes entregues.
## 11/11/2026 — obrigação PPC instalada emfbff2660
Instalação expressamente autorizada e executada pelo processo Git existente: `fbff2660f280f53fe079221164bb21917032ac70`, conferida às09:04:59Africa/Johannesburg(07:04:59UTC). Utilizável no simulador fiscal,fecho anual L8: **Preparar obrigação PPC**,com imposto anterior documental líquido de retenções,80%,três prestações arredondadas individualmente por excesso ao milhar e calendárioMaio/Julho/Setembro. Guarda e recuperação pelo rascunho privado existente e inclusão no relatório do dossier. Obrigação preparada não equivale a pagamento,nem cria dedução de PPC.
Backup novo/restauro isolado conferidos:190tabelas/369ficheiros. Catorze fontes do delta por hash e revisão,entrada,up,login,Concursos,fundos e simuladorHTTP200. Configuração preservada,zero migrações/importações;sete tabelas protegidas com totais/checksums idênticos ao backup:funding_programmes,funding_programme_versions,funding_programme_interests,desk_cases,simulation_form_drafts,labour_demands,training_capacities.275fichas/270instituições publicadas preservadas. Recibo `outputs/irpc-ppc-obligation-20261111/installation.json`; pacote `outputs/entrega-ppc-obrigacao-20261111.zip`,SHA256 `ab72a11b9306e4503ab92669c47ec592b2510aa01189ebab1d5216bb40d3769a`. Revisão6805b606 preservada para reversão.
Este recorte deixa de ser pendência de implementação/instalação. Conferência visual permanece separada. Próximo residual fiscal:PEC/obrigações e outros casos executáveis com fontes aceites;anualização/períodos especiais,dispensas/guias/juros e resíduos L1–L7/restanteL8/L9–L16 mantêm-se explícitos. Restantes módulos permanecem no saldo existente;portal não declarado integralmente concluído.
## 11/11/2026 — obrigação PPC ordinária de L8 em QA
**Pendência avançada:** o fecho anual existente passa a preparar a obrigação PPC2026 com liquidação documental do exercício civil completo2025: imposto anterior21(1) líquido de retenções,80%,três prestações iguais arredondadas individualmente por excesso ao milhar de meticais. Calendário por mês:Maio,Julho,Setembro,sem inventar dia limite. Fonte:RIRPC,Decreto9/2008,arts27(1)(a)/28(1–3),BR16/I16-04-2008,original SHA256 `fc373ff5637edf75ec6a9595dec8763a8503d4ee786e3b7d8f1c742eae9d80ee`, já preservado no acervo. Não usa colecta projectada corrente como liquidação anterior.
Reutiliza titular/exercício e rascunho privado existentes; formulário progressivo conserva edições deliberadas ao trocar enquadramento. Obrigação preparada e comprovativos efectivamente pagos permanecem distintos: não cria pagamento,dedução,saldo oficial,guia ou juros. Campos vazios,fonte ausente,divergência de titular/período,base negativa e período especial ficam indeterminados,sem impedir o fecho anual independente. Zero explícito permanece zero. Anualização28(3),dispensas,PEC e restantes regimes ficam separados.
**Prova sintética:** cálculo→POST/PUT→GET privado→recálculo→HTML/relatório do mesmo dossier em cinco cenários:72.000MZN ordinário,75.000após arredondamento,zero,valor desconhecido e período especial indeterminados. Saldo anual138MZN e comprovativos inalterados; histórico anterior conservado. Uma prova nativa/47asserções com rollback no QA55441 existente; cinco verificações novas de cálculo/interface e24afectadas aprovadas. Tipos/lint/compilação aprovados; pacote Git com aplicação/reversão conferidas no fecho. Provas `outputs/irpc-ppc-obligation-20261111/*-verification.json`.
**Estado:** implementado em QA, ainda sem instalação. Homologação mantém `6805b606`,Educação entregue e270instituições publicadas. Restam entrega Git deste delta e visual; L8 não está integralmente concluído. Preservam-se os resíduos L1–L7,restanteL8/L9–L16 e restantes módulos. Próximo trabalho fiscal: PEC/obrigações e demais casos executáveis com as fontes aceites,sem reabrir PPC ordinário.
## 11/11/2026 — retoma da revisão de Educação instalada em6805b606
Instalação autorizada pelo utilizador, executada pelo processo Git existente e conferida às08:46:05Africa/Johannesburg(06:46:05UTC): `6805b606a106fbed410b191ed778c0fad7c8e913`. Guardar e recuperar a preparação privada da revisão interna de procura/capacidade fica utilizável na homologação, pelo detalhe existente em `/management/education`. A gravação privada não decide nem aprova; preserva notas, versão analisada e a chave de uma intenção incerta.
Backup novo e restauro isolado conferidos:190tabelas/369ficheiros. Catorze fontes conferidas por hash; revisão,entrada,up,login,Concursos,fundos e simuladorHTTP200. Configuração intacta, zero migrações/importações. Totais e checksums inalterados nas sete tabelas protegidas:funding_programmes,funding_programme_versions,funding_programme_interests,desk_cases,simulation_form_drafts,labour_demands,training_capacities. Permanecem275fichas/270instituições publicadas. Recibo `outputs/education-review-preparation-20261111/installation.json`; pacote preservado `outputs/entrega-educacao-retoma-20261111.zip`, SHA256 `d2b7fd5e0d24baffd7b53d534c78e6e0f4492b47a40bb1d9d146b8a8b553caf5`.
Este recorte deixa de ser pendência de implementação e instalação. Conferência visual continua pendente; jornadas externas educativas e representação/consentimento documentados permanecem em aberto. Resíduos fiscais L1–L7/restanteL8/L9–L16 e restantes módulos mantêm-se no saldo existente. Próximo trabalho: primeiro residual executável dessa ordem, sem repetir este recorte nem as provas aceites.
## 11/11/2026 — retoma durável da revisão de Educação em QA
A revisão interna de procura e capacidade pode guardar e recuperar a preparação privada, incluindo notas, versão analisada e intenção de decisão incerta. Reutiliza os eventos de auditoria existentes, com conteúdo cifrado e isolamento por conta, tipo e declaração. Guardar não decide: a decisão continua pelo escritor nativo, com revisão independente e validação de versão. A intenção fica persistida antes do envio; uma resposta perdida recupera a mesma chave, sem duplicar a decisão. Edições posteriores e preparações mais recentes noutra janela não são substituídas silenciosamente.
**QA concluído, ainda não instalado:** cinco testes nativos/105 asserções e dez testes novos de interface, com os 17 afectados preservados; análise PHP e tipos aprovados. A página nativa recuperada foi exercitada no componente real em harness: recibo reconhecido, edição posterior conservada e zero envios adicionais. Prova `outputs/education-review-preparation-20261111/verification.json`. Isto não constitui conferência visual no navegador. Sem migração, tabela, papel ou concessão nova.
**Saldo:** falta entregar este delta pelo processo Git e conferir a apresentação no uso normal. Jornadas externas de estudantes, formadoras e empregadores, representação e consentimento documentados permanecem pendentes; Educação não é declarada integralmente concluída. A homologação mantém `9c067d6a` e as 270 instituições publicadas. Permanecem explícitos os resíduos fiscais L1–L7/restante L8/L9–L16 e os restantes módulos. As provas aceites não foram reabertas.
## 11/11/2026 — combinação CBF15/créditos/PEC instalada em9c067d6a
Instalação expressamente autorizada nesta retoma e executada pelo processoGit existente: `9c067d6ae89c0cd1e9ad4f2813e84048988d3319`, conferida às07:43:38Africa/Johannesburg(05:43:38UTC). Dezficheiros do delta conferidos por hash; revisão,entrada,login,up,Concursos,fundos e simuladorHTTP200; configuração preservada, zero migrações/importações. Backup/restauro isolado conferido:190tabelas e369ficheiros. Nenhum restauro sobre dados activos.
**Utilizável na homologação:** combinação CBF15corrente/transportado com créditos económicos/internacionais e saldos anteriores, com escolha dePEC limitada à colecta residual apósCBF15; gravação/recuperação e relatórios pelo percurso existente, conforme cinco cenáriosQA aceites. Os recortes anteriores de transporte e os históricos são preservados. Nenhum saldo oficial é consumido, pagamento/reembolso executado ou nova prioridade entre exercícios inventada.
**Preservação activa comprovada:** totais e checksums idênticos ao backup nas tabelas `funding_programmes`, `funding_programme_versions`, `funding_programme_interests`, `desk_cases` e `simulation_form_drafts`.275fichas,270publicadas; o original financeiro continua disponível. Não se extrapola esta comparação para tabelas voláteis de sessão/cache. Prova `outputs/irpc-cbf15-combination-20261111/installation.json`; pacote e reversão `outputs/entrega-irpc-combinacao-20261111.zip`, SHA256 `cebcd063b06f70a33b57f6cdd710478fb7aa3d18ef64aec4489cf855064bff99`.
Saldo: este recorte deixa de ser pendência de implementação/instalação. Conferência visual mantém-se separada; continuam repartição automática/múltiplos projectos, outros períodos/moedas/regimes/EP/cumulações/benefícios/formulários e os restantes resíduos L1–L7/restanteL8/L9–L16 e módulos não fiscais. O portal não é declarado integralmente concluído. A próxima implementação seguirá o primeiro residual executável do saldo existente, sem reabrir a combinação entregue nem a publicação das270instituições.
## 11/11/2026 — combinação CBF15 com créditos económicos/internacionais e PEC em QA
Retoma da ordem de conclusão existente, sem nova auditoria ou repetição da publicação financeira:270instituições publicadas e revisão instalada4f3dbcf8 preservadas; visual financeiro/S08 permanece separado. Reconciliação delimitada: o transporte CBF15 básico(7476dc34) já integra a revisão instalada4f3dbcf8, pelo histórico Git e recibos aceites; a nota antiga «QA, não instalado» desse transporte foi superada por entregas posteriores. Não reinstalar esse recorte como se estivesse ausente.
**Implementado nesta passagem, ainda sem instalação:** retirada a recusa técnica genérica da combinação de CBF15 corrente/transportado com saldos anteriores, reutilizando as fontes e ordem de dedução existentes. Créditos económicos e internacionais/correspondentes transportes precedemCBF15; PEC corrente e anterior usam apenas a colecta residual seguinte, antes de retenções/PPC. Uma escolha explícita de PEC superior ao residual é recusada como indeterminada, sem reduzir silenciosamente parcelas ou fabricar saldo recuperável. Histórico e fontes de investimento/uso anterior continuam preservados; a chave da combinação inclui versão própria para exigir recálculo dos resultados anteriores desse ramo, sem reescrever históricos nem invalidar os recortes sem combinação.
Cinco cenários sintéticos: combinação ordinária138MZN; quota residual documental100→saldo438; PEC sobrealocado→saldo indeterminado; CBF15 esgota colecta1338 ePEC seleccionado expressamente0→saldo−500 proveniente dePPC, sem reembolso/consumo oficial; janelaCBF15 expirada→saldo538. Também provada a varianteCBF15 corrente e o limite de quota após créditos anteriores. Cálculo→POST/PUT do rascunho privado existente→GET→recálculo exacto→HTML/dossier aprovados no QA55441/proactive_investment_20261027_test, com rollback e sem novas bases/portas/migrations/direitos. Uma provaAPI/37asserções; cinco testes novos nas combinações e23casos afectados de compatibilidade aprovados, não multiplicados como funcionalidades. Tipos/lint aprovados; compilação QA e pacote registados no fecho. Provas `outputs/irpc-cbf15-combination-20261111/*-verification.json` e relatórios recuperados.
**Saldo:** implementação e percurso desta combinação concluídos emQA; instalação do novo delta e visual separados. Persistem repartição automática/múltiplos projectos, outros períodos/moedas/regimes/EP/cumulações/benefícios e documentos fora dos recortes demonstrados; L1–L7/restanteL8/L9–L16 não são encerrados. Os cinco programas anteriores privados do directório financeiro não são publicados por consequência desta entrega. Próximo trabalho: agrupar a entrega compatível deste delta e continuar o primeiro residual fiscal executável, preservando os fechos aceites.
## 10/11/2026 —270instituições publicadas e consultáveis na homologação
**Cadastro e disponibilização do documento fornecido concluídos em homologação.** Conferência de leitura às22:54:31Africa/Johannesburg(20:54:31UTC), revisão instalada `4f3dbcf86499180011fc79712d6b5be445e57e26`:270/270instituições/operadores do documento publicados, nenhuma referência do original em falta,270identidades únicas,393referências físicas e168contactos. Publicações confirmadas pelo fluxo editorial nativo do utilizador; zero escritas efectuadas pelo verificador. As275fichas totais incluem cinco referências anteriores de fundos/programas que permanecem privadas, sem serem publicadas por consequência deste lote. Fonte original/SHA/edição, incertezas e identidades preservadas.
Consulta pública comprovada porHTTP200 e dados efectivamente devolvidos: `/fundos?kind=institution&include_historical=1` contém270resultados; filtro categoria seguros/tipo seguradora/presença física/Nampula contém seis(Fidelidade,Emose,HollardMoçambique,Britam,Arko,Palma). Fichas deBCI,Absa eArko abrem no componente público e conservam a fonte SHA256 `119bb5514d2777849b70aebfec21d28a41885cbdc7d9827fbebd764fe994c108`. A opção de incluir histórico não transforma uma instituição fundida/extinta em activa. Sede/balcões continuam separados de cobertura e produtos; dados desconhecidos não se tornam condições confirmadas nem garantias de financiamento/seguro.
**Saldo ACT-20261029-12 neste recorte:** conteúdo do documento cadastrado, publicado pelo editor e disponível por localização/categoria/tipo; já não é página vazia ou apenas códigoQA. Continuidade banco/seguradora/balcão/fonte/versão no mesmo dossier, gravação/recuperação e conservação após alterações continuam cobertas pelas provasQA aceites, reutilizadas sem criar dossier sintético em produção. Conferência visual de navegador permanece separada; não se presume instalação de outros módulos, fecho integral do portal ou conclusão dos resíduos fiscais. Nenhuma nova instalação/alteração de direitos exigida para declarar este resultado. Prova `outputs/funding-directory-20261110/institutions/publication-complete.json`.
## 10/11/2026 — recusa depois do login: navegadores e sessões diferentes
URL comunicado pelo utilizador:`/dashboard`. Conferência dirigida dos registos confirma login/MFA no **Edge** às22:03:53, seguido de`/dashboard`HTTP200 às22:03:55. A abertura seguinte de`/management/fundos/referencias-assistidas`às22:04:02/22:04:17 recebeu403 em **Safari/WebKit**, não no Edge. Existiam duas sessões da mesma conta: a sessão recente do Edge com confirmaçãoMFA válida e a anterior com confirmação antiga. A autorização editorial/verificação de email/perfil existentes estavam preservados. Logo esta nova recusa não comprova expiração do MFA recém-concluído no Edge nem falta de permissão da conta; o link abriu outro contexto de navegador.
Solução indicada: copiar e colar o URL do editor na barra do Edge já autenticado, mantendo a mesma aba nas retomas. Não repetir login por rotina, alterar permissões, desactivar verificações ou reinstalar por este motivo. **Reposição de acesso confirmada:** GETdo editor noEdge às22:11:21(+02),HTTP200. A recusa de entrada ficou delimitada e resolvida por uso da sessão correcta, sem nova instalação, concessão ou alteração de segurança. A instalação4f3dbcf8 permanece; publicação/conteúdo consultável ainda não declarados concluídos. Consulta estritamente de leitura; nenhum identificador de sessão/token/cookie/palavra-passe exportado e nenhuma sessão modificada. Prova:`outputs/funding-directory-20261110/institutions/access-context-diagnosis.json`.
## 10/11/2026 — retoma editorial instalada em4f3dbcf8
Correcção instalada pelo executor Git existente em `4f3dbcf86499180011fc79712d6b5be445e57e26`, confirmada às22:01:41Africa/Johannesburg (20:01:41UTC).7ficheiros conferidos por hash; revisão/entrada/login/up/Concursos/fundos HTTP200, configuração preservada, zero migrações/importações automáticas. Novo backup/restauro190tabelas/369ficheiros conferido, sem substituir dados activos. Pacote `outputs/entrega-instituicoes-retoma-20261110.zip`, SHA256 `9bce0250d546cd87f7c60a55f0c1d47fba74e8965f7d3224a588e140f87ec3cb`; anteriores preservados.
**Saldo activo:90rascunhos, zero publicações.** A indicação de publicação não foi confirmada no servidor por expiraçãoMFA/403. A correcção torna visível o estado e conserva os recibos na mesma aba, poractor/fontes; não publica por consequência da instalação nem altera o limiteMFA900s. Os90rascunhos anteriores ao armazenamento no cliente são recuperados idempotentemente na primeira passagem actualizada; após isso as fichas conferidas são saltadas nas retomas da mesma aba. Confirmações de revisão/publicação voltam a ser exigidas na entrada, e o backend revalida guardas/hash/versão. Fechar a aba/limpar armazenamento não elimina rascunhos, mas perde a retoma local. Estado alterado por outra janela exige conferência actual; o armazenamento do cliente não é prova de publicação.
Resta a conclusão autenticada da preparação/publicação e a consulta efectiva do conteúdo no destino. ACT12 continua aberto, visual pendente, sem reabrir APIEX/SWOT/S08 ou resíduos fiscais anteriores. Prova: `outputs/funding-directory-20261110/institutions/resume-installed.json`.
## 10/11/2026 — retoma após MFA, sem repetir fichas conferidas
Verificação activa posterior:90rascunhos/zero publicações; continuação GETMomentum403 às21:49:39 ePOSTpublicar403 às21:49:54 (+02). A sessão voltou a expirar. A publicação comunicada não chegou a ser executada pelo servidor. Nenhum rascunho foi removido.
Correcção delimitada emQA: a mesma aba conserva selecção, recibos/versionamento/hash e fundamento, separados por conta e conjunto de fontes; após reautenticação salta fichas já conferidas. As confirmações de revisão/publicação são desmarcadas na nova entrada, sem publicação automática. Uma publicação de resposta incerta conserva o mesmo corpo para recuperação idempotente. Novo comando de renovação utiliza logout/login/MFA nativos; limite900s e guardas mantidos. «Seleccionar todas as instituições(270)», contagem de progresso e controlos antes da lista recolhida tornam o estado visível. Sem migração, dados novos ou alterações de permissões. Alteração ainda não instalada nesta anotação; revisão activaef24cbdc.
Prova dirigida do componente efectivo: remonta a página como após autenticação, recupera selecção/recibos, não repete preparação, não publica automaticamente, recupera confirmação incerta e isola outra conta/fontes. Prova API editorial anterior1caso/27asserções reexecutada apenas pela propriedadeeditor_id acrescentada; provas270fichas e do dossier reutilizadas. Tipos/análise/compilação da entrega são registados pelo executor existente.
## 10/11/2026 — confirmação da preparação parcial e expiração do MFA
Após execução no editor, a homologação contém **32rascunhos e zero fichas publicadas**. A preparação gravou efectivamente e avançou por diferentes instituições. Os registos HTTP mostram preparação200 atéMyBucks às21:31:42, seguida deGETxtenda403 às21:31:43 ePOSTpublicar403 às21:31:59 (Africa/Johannesburg). A entrada no editor fora confirmada às21:16:43; corresponde ao limite existente de900segundos do MFA em `PortalProfileAccess::mfa`. Não é perda da instalação nem repetição de dados. Reautenticar no portal com MFA e retomar a preparação idempotente conserva os32rascunhos, sem substituir edições; publicação continua a exigir a confirmação separada. Nenhuma guarda, sessão ou permissão foi alterada. Revisão instalada `ef24cbdc`, ACT12 ainda sem conteúdo público confirmado. Prova de leitura: `outputs/funding-directory-20261110/institutions/publication-observation.json`.
## 10/11/2026 — instalação confirmada do directório financeiro
Revisão `ef24cbdc9218e2027f7d1a39968cc60869a35410` instalada na homologação às19:15UTC, pelo executor Git existente.33ficheiros conferidos por hash com o commit; configuração preservada, zero migrações/importações automáticas. Backup/restauro confirmado de190tabelas e369ficheiros. Entradas `/fundos?kind=institution`, `/concursos`, `/login`, `/up` e revisão HTTP200. Pacote anterior preservado; nova entrega `outputs/entrega-instituicoes-editorial-20261110.zip` SHA256 `168d7ffb6d054533674f3f07799959b46bc12641c045accfc1766da5cead9077`.
**Implementação instalada; conteúdo ainda sem publicação confirmada.** Leitura activa:zero fichas/zero publicações; tabelas sem RLS, logo o resultado não decorre de falta de contexto de leitura. Entrada autenticada no editor assistido confirmada nos registos; ainda sem pedidos POST de preparação/publicação observados. A indicação do utilizador de operação concluída permanece por reconciliar com o resultado efectivo, sem repetir a importação ou simular MFA. A conta existente tem designação editorial e perfil exigidos; nenhuma concessão alterada. ACT-20261029-12 permanece aberto até existirem fichas consultáveis. Visual e restantes resíduos do portal mantidos.
## 10/11/2026 — preparação editorial assistida das instituições
A autenticação SSH foi confirmada, mas não substitui a sessão e MFA recentes do editor no portal. Acrescentada `/management/fundos/referencias-assistidas`: selecção explícita das instituições do documento, conferência das fontes, preparação sequencial idempotente e publicação confirmada numa segunda operação. Preparar nunca publica. Fichas deliberadamente alteradas são preservadas e exigem revisão individual; mudança do conjunto de fontes interrompe a operação. Uma resposta de publicação perdida conserva versão/hash/fundamento para a mesma repetição. Expiração de MFA mantém as guardas e os resultados já confirmados.
Prova adicional delimitada:1caso API/27asserções,1caso frontend de separação preparação/publicação e recuperação da confirmação. Reutilizadas as5provas API/735asserções e28frontend anteriores; não se repetiu a carga das270instituições. Instalação e publicação no destino continuam distintas e serão registadas após confirmação efectiva. Sem migrações, novos direitos ou alteração das sessões existentes.
## 10/11/2026 — instituições por localização, categoria e tipo no processo do investidor
Pedido expresso da Tempo: inserir as instituições do documento agora indicado por localização/categoria/tipo e permitir que o investidor consulte bancos, seguradoras e outros intervenientes quando a necessidade surgir, sem limitar a decisão por falta de informação. **Implementação e conteúdo demonstrados em QA; nenhuma instalação ou publicação activa executada nesta passagem. Última revisão instalada confirmada:1c75a30a, APIEX/SWOT/Concursos preservados. ACT-12 não é encerrado pela preparação técnica.**
Original read-only `Notas/Instituições financeiras em Moçambique.docx`, edição Novembro2026, SHA256 `119bb5514d2777849b70aebfec21d28a41885cbdc7d9827fbebd764fe994c108`. Texto,509linhas de tabelas e nomes/contactos presentes nos parágrafos reconciliados. **270instituições/operadores,393referências de localização física e168referências de contacto**, não270ofertas de financiamento nem393moradas independentemente confirmadas. Instituições repetidas são reunidas por identidade explícita/aliases documentados ou número de registo citado; divergências, moradas alternativas, terceiros, dados históricos e identidades por confirmar permanecem identificados, sem substituir factos. Entidades sem morada continuam consultáveis como não localizadas. Totais estatísticos/redes incompletas não criam entidades ou agências fictícias. Listas antigas não confirmam licença actual; ABC permanece fundida/extinta segundo o documento, ligada à referência Access, sem atribuir a origem dos balcões por inferência.
Reutilizado o catálogo/versionamento/editorial existente: categorias banca,crédito/microfinanças,gestão de investimento,pensões,seguros/resseguros,mediação,câmbio,pagamentos e supervisão; uma entidade pode ter mais de uma categoria/função sem duplicação. Filtros por categoria,tipo e presença física são distintos da cobertura do produto. Província do cadastro é associada apenas por correspondência inequívoca; rua/localidade e precisão limitada conservam-se como declaradas. Pesquisa por cidade/balcão/morada/alias permite localizar informação textual (por exemplo Beira), sem geocodificação inventada. Informação por confirmar não é correspondência confirmada; ausência de registo não prova ausência de oferta. Oito gestoras de pensões continuam distintas de produtos de crédito, e seguradora não é apresentada automaticamente como financiadora/garantia de crédito.
**Continuidade no mesmo dossier:** ferramentas do investimento abrem a pesquisa contextual; seleccionam-se necessidade (incluindo seguros), instituição e, opcionalmente, sede/balcão. Guarda-se identidade estável, categorias/tipos, versão pública, local exacto escolhido, fonte/documento/SHA/linha, estado de confirmação e produto quando existente. Banco e seguradora coexistem; gravação repetida não duplica escolha. Actualização posterior dos pressupostos do investimento conserva a escolha; retirada da ficha pública não apaga a fotografia consultada. Dados/etapas/autoridade de decisão anteriores não são alterados. Consulta pública livre, operações privadas e edição editorial com as guardas existentes; nenhuma candidatura, email ou contacto enviado às instituições, novo perfil/grant/porta/schema ou ligação fictícia.
Provas no QA55441 existente, com rollback: **5casos API/735asserções únicas** (2desta fonte/608 e3de compatibilidade/127),28frontend dirigidos incluindo S08/navegação e recuperação da tentativa com balcão escolhido, tipos/PHPStan/lint/Pint/compilação QA aprovados. Uma tentativa de suite paralela foi recusada pela guarda da mesma base; compatibilidade executada sequencialmente sem remover a guarda ou criar outra base. `outputs/funding-directory-20261110/institutions/{content,verification}.json` e **`catalogo-qa.html` com270fichas ligadas**; renderização estática dos componentes, não conferência visual de navegador. Provas e pacote anteriores das11fichas/BNI Capital/FDEL mantidos. As7instituições comuns são enriquecidas pela mesma identidade; não há segundo directório. Fontes/documento original não foram alterados.
**Saldo concreto:** conteúdo e percurso local/categoria/tipo → banco/seguradora → fonte/contacto → mesmo dossier → continuação/recuperação demonstrados em QA. Restam instalação e disponibilização/revisão/publicação autenticada pelo fluxo editorial existente, consulta efectiva no destino e visual (conector indisponível). Preparação documental não publica lote automaticamente, conformeAGENTS.md; não declarar informação já disponível na homologação por existir este código. Restantes módulos e resíduosL1–L7/restanteL8/L9–L16 mantêm o saldo anterior.
### Consulta de bancos, seguradoras e outras instituições durante o processo
No investimento escolha **Instituições financeiras e apoios**; no menu comum há **Instituições financeiras e seguros**. Seleccione categoria/tipo e escolha entre **Sede, balcão ou presença física** e **Cobertura do financiamento/produto**. Use a localização do cadastro e, se necessário, palavras da cidade, balcão ou morada. Confira a fonte e as reservas: “reportado no documento”, “a confirmar”, “histórico”, “identidade por confirmar” e “localização por confirmar” não são equivalentes a confirmação actual de licença/oferta.
Na ficha, seleccione o dossier/oportunidade existentes, a necessidade (Seguros quando procura uma seguradora) e o balcão desejado, se identificado. Guardar conserva uma referência privada; não contrata seguro, não solicita empréstimo nem confirma garantia. No dossier, **Instituições e serviços ligados ao processo** apresenta banco e seguradora separadamente com categoria,tipo,balcão e origem. Os pressupostos/etapas seguintes não apagam essas escolhas; a ficha actual pode mudar mas a versão consultada fica no histórico privado. Não é preciso preencher um pedido externo para consultar os contactos.
## 10/11/2026 — ACT-20261029-12: directório financeiro com conteúdo em QA
**Homologação mantém1c75a30a; este conjunto não está instalado nem publicado.** A leitura autenticada do destino confirmou catálogo /fundos HTTP200, schema instalado e zero fichas/versões. O requisito não fica encerrado por essa disponibilidade técnica. APIEX/SWOT mantêm a entrega instalada própria, sem reabertura.
Localizada e reutilizada a lista fornecida em `Relatorios/benchmark de melhorias investeMoz.docx`, SHA256 `065203aacfae766c62cf9c2c272ebacc61ff23bd4ff9e91535939fede87f7617`, quadro de04/10/2026. Doze entradas documentais correspondem a **11fichas:7instituições e4programas/fundos** — BCI,Millennium bim,Absa,Standard Bank,Moza,BNI,Gapi,FECOP,Semeia Negócios,FCID VIII eFAE; **BNI Capital** integra os produtos do BNI, sem duplicar instituição ou presumir personalidade jurídica autónoma. Agregado genérico de linhas BNI não gera fundos fictícios. Referência FDEL anterior conservada, sem a contar novamente nesta entrega.
O cadastro/versionamento/competência editorial existentes passam a conservar natureza factual, identidade estável, contactos com fonte/data, locais físicos (sede/balcão/ponto institucional) e produtos próprios. Moradas não determinam cobertura financeira. Produto tem finalidade, instrumento, condições/montantes, disponibilidade e cobertura próprios; vazios são identificados como por confirmar. Cobertura e necessidade têm de corresponder ao **mesmo produto**, antes da paginaçãoSQL. Critério documentado pode excluir desconhecidos; consulta geral distingue resultados por confirmar de correspondência sustentada. FCID conserva seis províncias documentadas, sem confundir Maputo Província/Cidade; FAE mantém o Vale do Zambeze como âmbito textual com vínculo geográfico por confirmar, sem inventar distritos. A localização institucional do BNI não limita o financiamento a Maputo. Semeia conserva a chamada encerrada; a antiga linha BCI2018 não é reaberta. Condições e janelas históricas não se tornam ofertas actuais pela importação. Fontes indicadas consultadas para âmbito/disponibilidade; falha de recolha da Gapi não revoga o original, cuja data04/10 e disponibilidade por confirmar ficam explícitas.
Demonstrado em QA55441 existente: localização/necessidade → consulta das fichas reais fornecidas → fonte/contacto/condições → escolha privada no mesmo dossier e oportunidade → recuperação. Snapshot preserva versão pública e produto consultados mesmo após retirada/alteração da ficha. Repetição da intenção e recuperação após recarregar não duplicam a escolha/evento; outra oportunidade/actor e dossier encerrado mantêm guardas. Não envia candidatura, contactos ou dados às instituições, não concede crédito nem classifica o investidor automaticamente. Sem pontuação/IA, schema/base/porta nova, novas competências ou alteração de dados activos.
Provas dirigidas: **3casos API/127asserções e13frontend**, PHPStan nível7 sem supressões, tipos/lint/Pint/compilação QA aprovados. Testes transaccionais revertidos; sequências QA podem avançar. `outputs/funding-directory-20261110/verification.json`, `content-qa.json`, `dossier-qa.json`; **catálogo consultável de revisão** `outputs/funding-directory-20261110/catalogo-qa.html` e11fichasHTML ligadas. Renderização estática dos componentes afectados, não conferência visual de navegador nem publicação no destino. Provas anteriores preservadas e reutilizadas.
**Saldo ACT-12:** recorte funcional e conteúdo fornecido demonstrados em QA, preparado para disponibilização pelo fluxo editorial. **Restam instalação do conjunto, preparação/revisão/publicação autenticada das11fichas no destino e confirmação da consulta real pelo investidor**; visual continua pendente com conector indisponível. Não declarar ACT-12 encerrado ou o portal integral. Dados de sede/balcões, condições ou cobertura não documentados conservam o estado por confirmar; não bloqueiam os restantes dados fundamentados. Resíduos L1–L7/restanteL8/L9–L16 e outros módulos mantêm o saldo anterior.
### Utilização do directório financeiro
No dossier, escolha **Instituições financeiras e apoios**; o contexto da oportunidade é conservado. No catálogo /fundos seleccione **Local de interesse** e **Necessidade de financiamento**, pesquise e confira as mensagens de cobertura/condições por confirmar. Na ficha, leia separadamente presença física, cobertura do produto, fonte/data, contacto e disponibilidade. Escolha o produto e confirme apenas a conservação da referência privada; **Guardar e continuar no dossier** não envia candidatura. A secção **Referências de financiamento do dossier** conserva a fotografia consultada e permite abrir a ficha actual, que poderá ter mudado ou sido retirada. Resposta incerta permite repetir a mesma intenção sem duplicação; o dossier permite recuperar a escolha.
Para a equipa editorial: **Gerir fundos e programas → Preparar referências fornecidas** apresenta as referências documentais (incluindo FDEL anterior). Escolha cada ficha, confira a fonte, prepare o rascunho pelo botão existente e complete apenas dados fundamentados. Repetir a preparação abre a ficha já existente, sem substituir edições. Use **Alterar** nas secções de contactos/locais/produtos; guarde, confira a prévia da versão e publique com confirmação/motivo no fluxo existente. Não publicar linhas históricas como activas nem marcar oferta desconhecida como aberta. Não é necessário novo perfil, módulo ou cadastro de organizações duplicado.
## 10/11/2026 — APIEX, SWOT e acesso destacado a Concursos instalados
**Homologação instalada e conferida: `1c75a30a601ef00fc187ddf5a64183446d329e6f`**, https://teste-investidor.tempo.co.mz. Inclui APIEX/SWOT37854d02 e correcção do menu autorizada pela Tempo. **Concursos e anúncios** tem agora grupo próprio no menu horizontal comum, com consulta pública e acompanhamentos para utilizadores autenticados; endereço https://teste-investidor.tempo.co.mz/concursos. O módulo já estava activo e apenas a entrada estava dentro de Descoberta; não foi necessário activá-lo novamente. Um rascunho privado e zero versões publicadas preservados.
Conferência final:21hashes Git iguais à release, revisão HTTPS correcta e root/up/login/concursos HTTP200, configuração intacta, zero migrations/importações. Backup fresco restaurado com190tabelas/contagens/checksums iguais e369ficheiros/configuração conferidos;7476dc34 e pacotes anteriores preservados para reversão de código. Recibos `outputs/apiex-swot-concursos-delivery-20261110/{backup,restore,files-restore,verification,delivery}.json`; verificação UTC no recibo. Instalação executada pelo mesmo executor Git, sem alterações de permissões, dados reais, publicação de análises ou submissão externa.
**Saldo:** APIEX e SWOT abaixo deixam de ser pendência de instalação. Acesso pelas ferramentas **Preparação APIEX e SWOT** do dossier existente, conservando a oportunidade seleccionada. Conferência visual no uso normal continua pendente no mesmo registo; não é inferida das provas automáticas. Âmbitos normativos não mapeados continuam indeterminados; os restantes módulos e resíduos L1–L7/restanteL8/L9–L16 conservam o saldo anterior. O portal não é declarado integralmente concluído.
## 10/11/2026 — correcção dirigida das âncoras antes da promoção
O executor recusou c7901592 por um caso frontendS08 (1falha/1788), mantendo7476dc34 activo. Diagnóstico delimitado: a página tinha dois efeitos de âncora e o teste existente conservava uma única sequência de dependências. A abertura SWOT foi integrada no listener de hash já existente, também para mudanças posteriores de #tarefas, sem alterar a guarda/foco/expansão de S08. O teste S08 aceite conserva as suas asserções; acrescentado caso que alterna #tarefas/#S08, verifica isolamento no dossier e limpeza do listener.15casos dirigidos APIEX/SWOT/âncoras aprovados. Nenhuma alteração de dados, permissões, fontes normativas ou configuração; entrega autorizada retomada no mesmo mecanismo, sem mascarar a falha nem desactivar verificações. Recibos finais continuam a determinar a revisão instalada.
## 10/11/2026 — Concursos visíveis no menu principal; instalação agrupada autorizada
Conferência delimitada do destino7476dc34 por SSH autenticado: `procurement.enabled=true`, módulo disponível, tabelas presentes; `/concursos` e raiz HTTP200 com `procurementEnabled=true`. Preservados um concurso em rascunho e zero versões publicadas. O módulo não desapareceu nem estava desligado: a ligação estava dentro de Descoberta. Pedido Tempo «onde foram parar concurso? depois de resolver isso autorizo a instalação»: entrada promovida a grupo próprio **Concursos e anúncios** no menu horizontal comum, com **Consultar concursos e anúncios** e, após autenticação, **Os concursos que acompanho**. Retirada só a ligação duplicada de Descoberta; cinco atalhos principais e autorizações editoriais preservados. Não cria/publica concursos nem muda flags, dados ou schema.
Prova dirigida da navegação:20casos aprovados, incluindo visitante/utilizador, disponibilidade, destinos únicos e selecção correcta de catálogo/ficha/acompanhamentos. Tipos/lint/compilação QA aprovados. Observação anterior `outputs/investment-preparation-20261110/concursos-before.json`; pacote agrupado `outputs/entrega-apiex-swot-concursos-20261110.zip`, preservando o pacote APIEX/SWOT anterior. O conjunto APIEX/SWOT abaixo conserva as provas aceites, sem repetição. Instalação autorizada expressamente após esta correcção; execução e recibo final separados desta prova QA. Visual continua pendente por indisponibilidade do conector.
## 10/11/2026 — APIEX e SWOT no dossier existente: QA concluído, instalação pendente
**Instalado:** `7476dc343098f15a7982e9d7581bf4f59d43bc1a` em https://teste-investidor.tempo.co.mz, promovido às14:28:12UTC e conferido às14:29:07UTC (16:29 locais). Transporte CBF15 deixa de ser pendência de instalação. Delta14hashes Git/HTTPS, root/up/login/revision200, configuração intacta, zero migrations/importações; backup/restauro190tabelas e369ficheiros conferidos. Recibos `outputs/cbf15-carry-delivery-20261110/`; release anterior e pacotes preservados.
**Implementado em QA, ainda não instalado — APIEX:** dossier existente → requisitos fundamentados disponíveis/em falta/não aplicáveis/indeterminados → documentos existentes e complementos → revisão → prévia/ZIP com índice e anexos íntegros → registo da entrega externa, esclarecimento, resposta e decisão com data/referência/comprovativo. Dados do investidor/empresa/contexto/localização/oportunidade reutilizados. Estados em preparação/pronto para entrega/entrega registada/decisão recebida distintos; exportar não submete nem aprova. Snapshot preparado, versões, SHA e pacote privado cifrado preservados; recuperação devolve os mesmos bytes após alteração do contexto, revalidando acesso e segurança dos documentos. Ausência de recepção electrónica APIEX, segundo a orientação Tempo, não bloqueia este percurso; nenhum conector fictício ou formulário oficial inventado. Reutiliza o avaliador e modelos/fontes aceites; períodos/regimes fora do âmbito actualmente mapeado ficam indeterminados, sem conclusão normativa presumida.
**Implementado em QA, ainda não instalado — SWOT:** no mesmo dossier/oportunidade, objecto explícito investimento/território/sector/cluster, evidências do contexto/análise proactiva e Competitividade pública aprovada, quatro dimensões internas/externas, fonte/data/classificação de cada conclusão (facto documentado/hipótese/por confirmar). Sem pontuação, IA ou inferência de ausência pela falta de registos. Gravação/recuperação/histórico paginado e revisão pela equipa com permissões existentes. Alteração/retirada de fontes assinala revisão, conserva fotografia consultada e edições deliberadas. Acções ideia/oportunidade/parceiros/risco continuam no mesmo dossier; risco liga à tarefa nativa com origem própria e repetição sem duplicação. Privado por defeito; publicação exige o fluxo editorial existente, nunca automática. Contextos retirados ficam no histórico privado de leitura, sem reactivação.
Provas sintéticas no QA55441 existente, com rollback: seis casos API/211asserções;16casos frontend dirigidos (incluindo transporte e cabeçalho comum147páginas), tipos, análise estática e lint aprovados. `outputs/investment-preparation-20261110/verification.json`, ZIP/índice/anexos, recuperação SWOT e provas de isolamento/histórico/segurança. Pacote Git agrupado `outputs/entrega-apiex-swot-20261110.zip`, base exacta7476dc34, reversão de código sem apagar auditoria, pacotes privados ou tarefas posteriores. Nenhuma migration/base/porta/conta real nova, submissão real ou publicação. Compilação QA separada; provas anteriores reutilizadas.
**Saldo concreto:** APIEX e SWOT acima encerrados em implementação QA; instalação deste conjunto e conferência visual continuam pendentes. O conector mantém “The browser access host is unavailable”; SSR/testes não substituem visual, sem novo ciclo de reinícios. Os restantes módulos conservam o saldo anterior, incluindo resíduos L1–L7, restanteL8 e L9–L16. Isto não declara o portal integralmente concluído.
### Utilização — preparação APIEX e SWOT
Abra o dossier autorizado e escolha **Preparação APIEX e SWOT** nas ferramentas/documentos. Se houver várias oportunidades, escolha primeiro o contexto; o título e histórico identificam sempre essa escolha. Em APIEX, confira o resumo reutilizado e use **Alterar** para corrigir no editor existente. Confira os requisitos e os documentos disponíveis, complete os que faltam na biblioteca existente, guarde e reveja. A prévia apresenta o índice; exporte e conserve o ZIP. Registe depois a entrega efectivamente realizada externamente, com data, referência e comprovativo; esclarecimentos/respostas/decisão são eventos separados ligados à exportação. Uma decisão recebida não implica aprovação.
Na aba SWOT, escolha objecto/âmbito, consulte as evidências e preencha as quatro dimensões com fonte/data/classificação. Guarde, peça/recolha a revisão com as permissões actuais e associe uma acção à conclusão. A ligação de risco abre a tarefa no mesmo dossier, sem substituir tarefas anteriores. Histórico permite recuperar versões; restauro de SWOT cria novo rascunho sem eliminar entregas externas ou acções. Se uma gravação tiver resposta incerta, repita a mesma intenção ou consulte e compare a versão guardada; a substituição das edições locais é explícita. Fontes alteradas exigem nova revisão, sem reescrever conclusões. Análises ficam privadas; eventual divulgação passa pelo fluxo editorial existente.
## 10/11/2026 — transporte documental CBF15 em QA
Homologação mantém `d216830b`; este delta ainda não está instalado. Tipos/lint/Pint/buildQA aprovados; pacote com aplicação/reversão exactas,sem migrations/dados/credenciais. Pendência encerrada no recorte normal residente2026/ano civil/MZN: crédito de investimento de origem anterior do mesmo contribuinte/projecto/local/regime, com activo/documento/parcela, exercício de origem, NUIT/projecto de origem, taxa original e qualificação históricas documentados. Exercício de origem anterior a2026,dentro da janela original e não anterior à aquisição; no projecto em funcionamento,a aquisição não precede o início aplicável. Não renova o prazo nem recalcula origem a outra taxa. Valor corrente/ausência e uso efectivo agregado exigem reconciliação explícita; histórico de simulações não equivale a utilização fiscal.
Cálculo → POST/PUT do mesmo rascunho privado → GET → recálculo → HTML/dossier demonstrados no QA55441 existente com rollback. Exemplo sintético recuperado: nominal de origem4.000,uso efectivo anterior1.000,disponível3.000,quota documental do projecto200 → dedução200,não utilizado2.800,saldo500MZN. Início2022 → quinto exercício2026; mudando o início para2021 → sexto,parcela expirada3.000,disponível/dedução0,saldo700,com activo,uso anterior e origens preservados. Seis/sete resultados históricos conservados,incluindo o resultado ordinário aceite. Provas `outputs/irpc-cbf15-carry-20261110/{carry,expired}-verification.json` e relatórios respectivos; API um caso/28asserções novas,30casos frontend nas fronteiras alteradas incluindo formulário real compilado e prevenção de duplicação. SSR/formulário simulado não é navegador/visual. FonteCBF15(4/7),APIEX2025 PDF71–72 já aceite,reutilizada.
A preparação mantém os activos correntes e exige explicitar os dados em falta; activos de origem e correntes partilham a capacidade documental e a detecção de duplicação entre mapas. Origem com outro NUIT/projecto/taxa,exercício incoerente,uso superior ao nominal ou declaração de ausência incompatível não produz dedução. Sem pagamento,reembolso,consumo oficial ou criação automática de saldo transportado. Livros antigos permanecem nos formatos1/2; só o transporte preparado usa livro3/detalhe2,para o código anterior recusar em vez de ignorar o crédito novo. Snapshots e históricos aceites não são reescritos; reversão é de código e não deve sobrescrever dados posteriores.
Saldo preservado: outros projectos/localizações/regimes/cumulações/EP/L7,combinação positiva com transportes económicos/internacionais/PEC anteriores,repartição automática,benefícios/formulários oficiais e restantes resíduos L1–L7/restanteL8/L9–L16 mantêm os seus recortes. Os módulos não fiscais mantêm o saldo existente,sem nova auditoria geral. Próximo trabalho: primeira combinação residual executável respeitando a ordem de dedução e quota do projecto,ou percurso independente do saldo existente; visual continua pendente no mesmo registo. Não é conclusão integral do portal.
## 10/11/2026 — menus horizontais instalados
**Homologação: `d216830b068af8a7a40d43993795639e73aaed78`**, promovida às13:56:27UTC e conferida às13:56:54UTC (15:56 locais), em https://teste-investidor.tempo.co.mz. Delta10hashes Git e revisão HTTPS iguais; root/up/login/revision HTTP200, configuração preservada, zero migrations/importações. Backup/restauro190tabelas com contagens/checksums iguais e369ficheiros/configuração conferidos; revisão d5cb41b1 preservada para reversão. Recibos `outputs/horizontal-menu-delivery-20261110/{backup,restore,files-restore,verification,delivery}.json`. Pacote anterior e dados/históricos preservados; sessãoSSH pronta para continuação autenticada, sem credencial em disco.
O recorte dos menus horizontais abaixo deixa de ser pendência de instalação: cabeçalho comum em páginas públicas/autenticação/definições/privadas, sem sidebar global concorrente; Descoberta sem repetição dos cinco atalhos mantidos na entrada principal. Prova estrutural dos146layouts e23casos dirigidos reutilizada; não é conferência visual. O conector continua indisponível,sem novos reinícios ou alterações ao navegador. Visual mantém-se pendente no mesmo registo. O restante saldo de funcionalidades por módulo e L1–L7/restanteL8/L9–L16 não é encerrado por esta instalação.
## 10/11/2026 — janela CBF15 instalada; menus horizontais uniformizados em QA
**Homologação: `d5cb41b111cac95c431a3072b8c4cc0141481ec2`**, promovida às13:43:40UTC e conferida às13:44:18UTC (15:44 locais). Delta12hashes Git e revisão HTTPS iguais; root/up/login/revision HTTP200, configuração preservada, zero migrations/importações. Backup/restauro190tabelas e369ficheiros/configuração conferidos,89e6d72c preservada. Recibos `outputs/cbf15-window-delivery-20261110/`. Janela de cinco exercícios CBF15 deixa de ser pendência de instalação; transporte de créditos anteriores e restantes resíduos fiscais continuam separados.
**Delta posterior em QA, ainda não instalado — pedido expresso da Tempo10/11, corrigido para menus horizontais com dropdown:** cabeçalho comum passa a ser montado uma vez pelo layout global em todas as páginas; área privada usa o mesmo cabeçalho sem navegação lateral concorrente. Autenticação, definições, gestão e páginas públicas conservam conteúdo e breadcrumbs; filtros de capacidades, direitos e destinos privados existentes preservados. Esconder cabeçalho na impressão mantém-se. Os cinco atalhos (Porquê investir,Partir de uma ideia,Consultar oportunidades,Simulador Fiscal,Sectores) ficam intactos na entrada principal. Descoberta conserva localizações/mapa,catálogos e módulos opcionais,sem repetir os atalhos; Partir de uma ideia já estava fora desse grupo. CSS responsivo e implementação comum dos dropdowns/teclado/Escape/toque reutilizados,sem novo menu alternativo.
Conferência estrutural dos layouts das146páginasTSX existentes: exactamente um cabeçalho comum por composição, incluindo páginas privadas,autenticação/definições, sem sidebar global; conteúdo/contexto/breadcrumbs e cinco atalhos preservados. Provas `tests/frontend/portal-horizontal-shell.test.mjs`, navegação e renderização dirigida (23casos aprovados). É SSR com dependências de UI substituídas,não prova visual de navegador. Tentativa única de inventárioCUA nesta continuação devolveu exactamente `Browsers: Error: The browser access host is unavailable`; nenhuma alteração/reinício de navegador. Conferência visual mantém-se pendente no registo existente. Tipos/lint/build QA aprovados, logs desta entrega; base activa/dados/permissões intocados.
O saldo integral dos restantes módulos e L1–L7/restanteL8/L9–L16 permanece no plano existente; nenhuma contagem de páginas/testes declara o portal concluído.
## 10/11/2026 — janela de cinco exercícios CBF15 em QA
Homologação mantém `89e6d72c`; o delta seguinte ainda não está instalado. Pendência encerrada em QA: contagem inclusiva dos cinco exercícios civis do CBF15(4/7) na liquidação2026, para investimento efectivo do próprio2026. Início de exploração para projecto novo; início dos procedimentos após aprovação para projecto em funcionamento. Não usa cinco anos móveis nem reinicia o prazo pela data de aquisição. Início2022, mesmo em31/12 → quinto exercício2026, dedução200 pela quota residual, saldo500MZN. Início2021 → sexto exercício2026, prazo expirado, dedução0 e saldo700; activo e documento preservados. Datas inválidas/futuras e activos antigos apresentados como investimento corrente permanecem indeterminados/recusados, não zero por omissão.
Cálculo → POST/PUT do mesmo rascunho privado → GET → recálculo → HTML/dossier demonstrados no QA55441 existente, com rollback, cinco/seis resultados históricos e todos os anteriores preservados. Provas `outputs/irpc-cbf15-window-20261110/{fifth,expired}-verification.json` e relatórios respectivos. Modelo `IRPC-CBF15/2`, livro existente versão2 sem migration; snapshots aceites não são reescritos. Fonte já aceite APIEX2025 PDF71–72,CBF15(4/7), reutilizada sem nova validação de autenticidade. Provas anteriores reutilizadas; apenas fronteiras alteradas conferidas.
Saldo: transporte de créditos de investimento de anos anteriores, repartição automática/múltiplos projectos, EP/L7/cumulações/outros benefícios/formulários oficiais continuam por implementar nos respectivos recortes. A janela corrente não declara o transporte entregue. Resíduos L1–L7, restanteL8 e L9–L16 e saldo dos módulos não fiscais preservados. Conferência visual pendente no mesmo registo; portal não integral. Próximo trabalho executável: transporte documental CBF15 dentro da mesma janela, com consumo anterior reconciliado e sem duplicar o investimento corrente.
## 10/11/2026 — CBF15 instalado na homologação
**Revisão instalada: `89e6d72c1e86df8eb86e7be74583659975585036`**, promovida às13:27:59UTC e conferida às13:28:21UTC (15:28 locais), em https://teste-investidor.tempo.co.mz. Os17hashes do delta coincidem com Git; revisão/root/up/login HTTP200, configuração preservada, zero migrations/importações. Backup fresco/restauro190tabelas com contagens/checksums iguais e369ficheiros/configuração extraídos e conferidos;7c8d4726 preservada para reversão. Recibos `outputs/cbf15-delivery-20261110/{backup,restore,files-restore,verification,delivery}.json`; pacote imutável `outputs/entrega-cbf15-20261110.zip`, SHA25613bdecef2697edcc821d6fa490586d0c2f4b171b4308c655aa60ba5632664c83.
O recorte CBF15 abaixo deixa de ser pendência de instalação: primeiro exercício2026, residente geral/MZN, activos e condições documentados, colecta integral do projecto ou quota residual documentada, cálculo/gravação/recuperação/relatórios com histórico. As notas QA anteriores ficam como histórico das provas aceites. Outros exercícios/transporte/caducidade, repartição automática/múltiplos projectos, EP/L7/cumulações/outros benefícios/formulários e restantes resíduos L1–L7/L8/L9–L16 permanecem no plano existente. Próximo trabalho: residual executável de CBF15/L8, preservando o recorte instalado e sem repetir as provas aceites. Conferência visual continua pendente no mesmo registo; portal não declarado integral.
# Rastreabilidade, utilização e formação
## 10/11/2026 — acordo PT–MZ instalado; crédito CBF15 demonstrado em QA
**Homologação: `7c8d4726e9f93325480e2ee6e5e790e9adbcaf19`**, promovida às12:36:07UTC e conferida às12:37:11UTC (14:37 locais). Revisão HTTPS/10hashes iguais, quatro entradasHTTP200; configuração preservada, zero migrations/importações. Backup/restauro190tabelas e369ficheiros/configuração conferidos, b10b35f3 preservada. Recibos `outputs/cdt-agreement-delivery-20261110/`. Falha inicial de recepção Git delimitada ao directório objects/7c pertencente aroot: apenas proprietário/grupo ajustados a conta do serviço,modo0755 inalterado; pedido original validado recolocado sem alterar corpo/SHA ou controlos. Recibos git-permission-repair/request-retry; não foi falha do portal nem alteração de segurança do navegador. Sessão SSH preservada para continuação autenticada, sem credencial em disco.
**Pendência efectivamente encerrada em QA, ainda sem instalação:** CBF15, crédito fiscal por investimento efectivo no primeiro exercício2026, residente geral/ano civil/MZN, colecta inteiramente da actividade do projecto ou quota residual documentada após créditos anteriores e sem outros benefícios próprios/cumulações ou transportes positivos combinados. Reutiliza qualificação CBF de L7 e fonte APIEX já aceite (recibo irpc-l7-common-qa-20261026,compilação oficial2025,PDF66–72,art15PDF71–72), não importação como activação. Localização do investimento,aprovação/condiçõesCBF3/9/10/regime genérico13,início aplicável e activos/documentos exigem correspondência própria. Activo novo corpóreo,nacional,produtivo e fora15(6); condições negativas/exclusões documentadas produzem crédito zero nessa linha,preservando os dados. Informação em falta não é zero.
Percurso completo: preparar detalhe → calcular após económico/internacional e antesPEC/RF/PPC → gravar/actualizar pelo writer privado existente → recuperar → recalcular → HTML/dossier com fornecedor/documento/parcela/valores/qualificação/período/histórico. Maputo cidade5%: investimento10.000 → crédito500 → saldo200 MZN. Restantes províncias10%: crédito1.000 → saldo−300, recuperável simulado devido aoPPC. Cenário de limite: nominal4.000,dedução1.400,crédito não usado2.600,PEC não utilizado200,PPC500; nenhum reembolso de crédito/PEC ou saldo oficial criado/consumido. Provas `outputs/irpc-cbf15-20261110/{city,province,capped,partial}-verification.json` e relatórios respectivos. Quatro versões do mesmo rascunho API em transacção revertida QA55441,com histórico anterior e versões seguintes preservados; zero alterações activas.
Quota residual documentada: nominal4.000, limite do projecto200, dedução200, não utilizado3.800 e saldo500 MZN; recuperada pela API com quatro resultados históricos. Quota superior à colecta global remanescente, negativa ou sem fundamento é recusada. Não há repartição automática de rendimentos/custos/perdas. Tipos, lint/Pint e compilação QA aprovados; provas anteriores reutilizadas.
Livros anteriores L8 permanecem versão1,com chaves/resultados inalterados quando sem CBF15. Só o livro com este novo detalhe usa versão2: cliente anterior recusa o formato,conservando o snapshot,em vez de recalcular ignorando o novo benefício. Não é migration de base. O resumo antigo dos benefícios fica conservado e não reaplicado; ausência dos restantes benefícios exige declaração/reconciliação explícita,mesmo quando CBF15 é ausente. Fonte documental tem capacidade por factura/parcela e entre mapas da entidade; mudar id/data não duplica investimento. Repetir simulação não é uso fiscal anterior.
**Saldo preservado:** CBF15 de outros exercícios/transporte/caducidade,repartição automática/múltiplos projectos,EP/L7/cumulações,outros benefícios e obrigações/formulários oficiais continuam separados. Ausência/zeroCBF15 não bloqueia os saldos anteriores aceites. Restantes L1–L7/L8/L9–L16 e módulos não fiscais mantêm o saldo existente; o portal não está integral. Visual permanece pendente. Conjunto CBF15 preparado para entrega sobre7c8d4726,sem instalação nesta entrada.
## 10/11/2026 — b10b35f3 instalado; acordo institucional PT–MZ em QA
**Homologação: `b10b35f30830922dae00afde50ac2bdb0e8cef6f`**, promovida às12:15:13UTC e conferida às12:16:31UTC (14:16 locais). Revisão HTTPS e16hashes Git iguais, quatro entradasHTTP200; configuração preservada, zero migrations/importações. Backup/restauro190tabelas com contagens/checksums iguais e369ficheiros/configuração conferidos;60b52fc8 disponível para reversão. Recibos `outputs/government-fraction-delivery-20261110/{backup,restore,files-restore,verification}.json`. Sessão SSH mantida aberta para continuação autenticada, sem credencial em disco. Isenção por devedor público11(3)a e fracções económicas L6 deixam de ser pendências de instalação; as anotações anteriores QA são históricas nesses recortes.
**Delta posterior QA, ainda não instalado:** correspondência11(3)b para instituição/organismo beneficiário corrente que concedeu o financiamento no âmbito de acordo concluído entre os governos PT–MZ. Beneficiário, financiamento ligado aos juros e acordo/cláusula aplicável exigem suporte próprio. País, nome da entidade ou qualificação institucional isolada não activam isenção. Fundamento distinto do devedor público11(3)a; limpar/trocar a escolha invalida a decisão corrente, sem apagar resultado histórico nem reutilizar campos ocultos como nova decisão.
Cálculo → gravação/GET pela API existente no QA55441 com rollback → recuperação → HTML/dossier demonstrados. Exemplo sintético: juros brutos60, imposto pago10 e fracção nacional8, com acordo qualificado → limite convencional0/crédito0/saldo708 MZN; sem reembolso, transporte ou consumo oficial e sem alterar L6. Relatório conserva instituição, financiamento e acordo. Prova `outputs/irpc-pt-mz-agreement-20261110/verification.json`; tipos/lint/Pint/build QA aprovados e provas anteriores reutilizadas.
Pacote `outputs/entrega-cdt-acordo-20261110.zip`, base exactab10b35f3, aplicação/reversão conferidas; entrega seguinte expressamente autorizada durante esta continuação, ainda sem instalação nesta entrada. Restantes beneficiários/regimes11(3)b, EP, relações especiais e demais CDT/benefícios/obrigações conservam correspondência própria; saldo L1–L7/restanteL8/L9–L16 preservado. Conferência visual continua pendente no mesmo registo; portal não integral.
## 10/11/2026 — CDT instalada; isenção governamental e precisão económica em QA
**Homologação: `60b52fc8c7446cbbee904a4411eca8ad284aa7ad`**, promovida às11:49:26UTC e conferida às11:50:11UTC (13:50 locais). Delta13hashes Git e revisão HTTPS iguais; portal/login/up/revision HTTP200, .env preservado, zero migrations/importações. Backup fresco/restauro190tabelas com contagens/checksums iguais e369ficheiros/configuração extraídos/conferidos; ac67f9c1 disponível para reversão. Recibos `outputs/cdt-pt-delivery-20261110/{backup,restore,files-restore,verification}.json`. Sessão SSH encerrada, credencial não guardada. Royalties/dividendos/juros ordinários PT–MZ deste recorte deixam de ser pendência de instalação; não reabrir provas aceites.
**Novos fechos dirigidos apenas em QA, ainda não instalados:**
- CDT11(3)a: devedor Estado português, subdivisão política/administrativa ou autarquia, expressamente qualificado e documentado; mantém residência, fonte, definição, período e restantes condições do recorte. Limite convencional0, imposto pago10 sem crédito elegível e saldo708 MZN no exemplo. Não gera reembolso/transporte nem altera L6. Beneficiários/acordos11(3)b e outras situações permanecem separados. Prova `outputs/irpc-pt-mz-government-20261110/verification.json`, com API/gravação/recuperação/HTML/dossier e fundamento do devedor no relatório.
- L6: diferença económica conserva fracções exactas, incluindo subcêntimos e n/d, pelo mecanismo racional existente. Remove a limitação técnica de exigir cêntimos inteiros neste mapa; não cria regra de arredondamento fiscal. Montante do tópico é reconciliado por igualdade racional, evitando tratar0,006 como0 ou aceitar truncamento. Exemplo de IRPC subjacente20,01: nominal12,006, efeito112,006, base4512,006, colecta1443,84192; reconferida L8, saldo723,83592. Repetição/substituição não acumula; valores já considerados/históricos preservados. Prova `outputs/irpc-economic-fraction-20261110/verification.json`, até API/recuperação/HTML/dossier/L8. Outros mapas e arredondamento de declarações oficiais conservam âmbito próprio.
Conjunto novo agrupável sobre60b52fc8; não integra a revisão instalada. Conferência visual permanece pendente no mesmo registo S08. Resíduos L1–L7, restante L8 e L9–L16 preservados; nenhum lote ou portal declarado integral. Próximos ramos: CDT11(3)b e restantes correspondências concretas, obrigações/benefícios e demais percursos conforme saldo existente.
Conjunto posterior preparado em `outputs/entrega-isencao-precisao-20261110.zip`, base exacta60b52fc8. Tipos/lint/Pint/build QA aprovados, aplicação/reversão conferidas em índices temporários. Não instalado; provas e pacotes anteriores preservados.
## 10/11/2026 — económico instalado; CDT Portugal avançada em QA
**Homologação: `ac67f9c13a6561b66c5da788a48849beac90a905`**, promovida pelo executor Git existente às11:32:14UTC e conferida às11:33:16UTC (13:33 locais). Revisão HTTPS e27hashes Git iguais; portal/login/up/revision HTTP200; .env preservado, zero migrations/importações. Backup/restauro190tabelas com contagens/checksums iguais e369ficheiros/configuração extraídos/conferidos. Release c359364d conservada. Recibos `outputs/economic-special-delivery-20261110/{backup,restore,files-restore,verification}.json`. Credencial não guardada; sessão SSH encerrada. Inclui quota económica, inclusão/reconciliação ordinária e especial L6, partilha64(3) e associações64(4). Entradas anteriores «apenas QA» são históricas nestes recortes; não reinstalar por consequência.
**Delta posterior, ainda QA:** correspondência CDT PT–MZ para royalties ordinárias não digitais, dividendos e juros ordinários de fonte portuguesa de residente convencional em Moçambique. País não activa a convenção. Exige beneficiário efectivo, definição/natureza, fonte/pagador, ausência de EP/instalação fixa ligada, período e suporte documentados; relações especiais verificadas nos juros/royalties, isenção governamental11(3) separada. Limite10% do bruto nos ramos suportados; dividendos usam alteração do Protocolo2009 art6, evitando taxa15% do texto1992. Não usa retenção L5 em sentido inverso nem tax sparing presumido. Mantém limite manual/históricos e parcela líquida nacional.
Prova sintética por ramo: bruto60 → limiteCDT6, imposto pago10/fracção nacional8 → crédito6 → saldo702 MZN. Cálculo/gravação/recuperação/HTML/dossier por API existente no QA55441; sem novo writer, schema, direito ou escrita activa. Provas `outputs/irpc-pt-mz-{credit,dividend,interest}-20261110/verification.json`. Fracções mantêm exactidão, sem arredondamento fiscal presumido. País/condição/período inválidos deixam apenas o caso dependente indeterminado. Este delta não integra ac67f9c1.
Restam CDT de outros Estados/naturezas, EP/instalação fixa, juros governamentais, excessos/relações especiais e casos fora do recorte, além dos demais resíduos L1–L7/L8/L9–L16. Conferência visual continua pendente no mesmo registo. Não declarar L8 ou o portal integralmente concluídos.
Conjunto CDT agrupado em `outputs/entrega-cdt-pt-20261110.zip`, base exacta ac67f9c1, aplicação/reversão conferidas; tipos/lint/Pint e build QA aprovados. Instalação deste delta ainda não executada.
## 10/11/2026 — crédito económico: partilha e associações em QA
Homologação mantém `c359364d`. Avanço posterior em QA, sem instalação: art64(3) distingue a parcela elegível do rendimento da partilha e o excesso com natureza de mais-valia; art64(4) distingue associação em participação e à quota, exigindo tributação efectiva documentada na participação. Reutiliza cálculo60%, prova do imposto subjacente, limites, prioridades, isolamento e writer privado existentes. Não calcula a mais-valia como crédito nem pressupõe inclusão L6 ou reconferência da colecta. Campos antigos e históricos são preservados; fontes incompletas ficam recuperáveis e indeterminadas.
Critério demonstrado: calcular → gravar/GET pela API existente no QA55441 com rollback → recuperar exactamente → HTML e relatório do dossier. Partilha sintética: valor atribuído200, entradas100 e rendimento documentado150 → elegível100/excesso50 → quota35,84/crédito12/saldo688 MZN. Associação documentada100/imposto20 → crédito12/saldo688 MZN, sem alterar L6. Provas `outputs/irpc-economic-liquidation-20261110/verification.json` e `outputs/irpc-economic-association-20261110/verification.json`. Não são actos fiscais nem consumo de saldos oficiais.
Conjunto de fontes compatíveis com quota e inclusão ordinária agrupado em `outputs/entrega-economica-especial-20261110.zip`, base c359364d, aplicação/reversão conferidas em índices temporários. Pacotes anteriores intactos. Inclusão/correcção especial em L6 também demonstrada em QA: reconcilia rendimento total da partilha (incluindo mais-valia) uma só vez, enquanto a quota usa apenas a componente elegível; associações reutilizam a qualificação própria. Prova outputs/irpc-economic-special-inclusion-20261110/verification.json: efeito162, base4562/colecta1459,84, quota35,84/saldo739,84 recuperados na API e relatórios. Casos transitórios, fracções de cêntimo, CDT, benefícios/obrigações e restantes resíduos L1–L7/L9–L16 continuam no saldo. Conferência visual pendente. Próximo trabalho funcional: correspondência CDT automática nos casos suportados pelas fontes existentes; eventual instalação deste conjunto é acção separada. Tipos/lint/Pint/build QA conferidos no conjunto final.
## 10/11/2026 — inclusão/correcção económica L6 em QA
Instalado: `c359364d`. Quota económica e inclusão/correcção L6 ordinária demonstradas em QA, sem instalação. Pacote anterior `outputs/entrega-economica-l6-l8-20261110.zip` preservado; novo delta posterior agrupado separadamente. Conferência visual pendente no registo existente.
Escolha explícita da origem L8, reconciliação de lucros/crédito já considerados, prévia da diferença e confirmação precedem o cálculo/gravação existentes. Repetição substitui a parcela; não acumula. Prova: +112 MZN leva lucro tributável a6812, base4512 e colecta1443,84. Montantes já incluídos100/12 dão efeito0; excesso150/20 corrige−58. Origem L8 anterior fica desactualizada; reconferida a nova colecta, quota35,84 e saldo723,84 mantêm-se na API/recuperação/HTML/dossier, com históricos preservados. Provas `outputs/irpc-economic-inclusion-20261110/verification.json` e `outputs/irpc-economic-quota-20261110/verification.json`. Tipos/lint/Pint/build QA aprovados.
Âmbito ordinário residente geral2026/MZN. Fracções de cêntimo exigem tratamento próprio, sem arredondamento presumido. Casos especiais, CDT e obrigações continuam no saldo. Resíduos L1–L7 e L9–L16 preservados; L6/L8 não integrais. Registos anteriores abaixo são históricos.
## 10/11/2026 — conjunto Educação/L8 instalado e residual económico avançado
**Instalado e conferido em c359364de3f65599f2248540a5b040604e8490cc**, por fast-forward normal em main a partir de ff726e57. Árvore058910eb26f566c0486a20c1e7fc12f06bbe06a5 igual ao pacote QA. Executor existente terminou às10:54:50UTC; revisão HTTPS/portal/login/upHTTP200 e14hashes de fontes conferidos às10:55:21UTC. Zero migrations/importações; configuração idêntica; release anterior preservada. Backup fresco em `/var/backups/investmoz-education-l8-20261110/backup`,190tabelas efectivamente restauradas/checksums iguais num contentor sem rede,369ficheiros/configuração restaurados e conferidos em pasta privada. Credencial não guardada; sessão SSH encerrada após conferência. Recibos `outputs/education-l8-delivery-20261110/{backup,restore,files-restore,verification}.json`. Pacotes selados anteriores preservados. Controlos do executor obrigatórios,sem nova ronda manual de provas aceites.
Ficam utilizáveis no destino os dois recortes: revisão educativa conserva preparação/envio incerto ao consultar histórico; crédito internacional oferece derivação ordinária da fracção nacional. Instalação destes recortes deixa de ser pendência;conferência visual continua separada. Outros critérios dos módulos não são encerrados por esta instalação.
**Próximo residual executável avançado após a instalação — QA,sem instalação nova:**quota proporcional do crédito económico ordinário,CIRPC64(1–2)após acréscimo53. Fonte aceite Lei34/2007,PDF18/p768-(72),reutilizada;60% do imposto correspondente e qualificação/exclusão40 já validados pelo mecanismo existente. Novo modo calcula `colecta L6 × (lucros incluídos + crédito nominal já acrescido) / matéria colectável L6`,com versão/origem/referência no resultado e relatório. Não altera a matéria colectável,não duplica o acréscimo,não calcula automaticamente a sua inclusão. Modo documentado antigo por omissão;limite anterior conservado,não utilizado no modo derivado. Fonte/base desactualizada,acréscimo incompleto,correspondência ausente e soma de componentes acima da base impedem o saldo dependente. Casos64(3/4),L7 e outras situações conservam o saldo próprio.
Quatro casos novos frontend,cálculo/recuperação/HTML/dossier;API privada existente,num novo caso/9asserções,DatabaseTransactionsrollback na mesma QAproactive_investment_20261027_test:55441,actor1021existente. Novo exemplo sintético:lucros100+acréscimo60,base4400/colecta1408→quota256/5=51,2MZN;saldo3244/5=648,8MZN.Sem saldo fiscal consumido. Prova `outputs/irpc-economic-quota-20261110/verification.json`. Tipos/lint ePint aprovados;buildQA em log próprio. Nenhuma prova anterior de Educação/fracção nacional reaberta. Estado é QA,ainda não instalado nem visualmente aceite. L8 permanece parcial;resíduosL1–L7 eL9–L16 mantidos. Próximo:inclusão/correcção económica na base com a correspondência própria e restantes ramos já identificados,sem repetir os recortes fechados.
## 10/11/2026 — L8: fracção nacional ordinária demonstrada até ao relatório em QA
Pendência funcional encerrada **em QA no recorte ordinário**: opção explícita de derivar a fracção nacional do crédito internacional da colecta L6 corrente e da parcela líquida estrangeira correspondida à matéria colectável. Fonte aceite reutilizada: CIRPC65(1)(a/b),65(2),Lei34/2007,PDF18/p768-(72),e ordem66/RIRPC21. Não se exige nova validação da fonte oficial. A fórmula técnica `colecta antes de créditos × parcela líquida / matéria colectável` fica limitada à colecta normal corrente, base positiva, inclusão documentada, mesmo titular/exercício/país e afectação líquida identificada. O portal não inventa nem reparte custos/perdas/deduções; valores acima da base ou parcelas por país que em conjunto excedam a base impedem o saldo dependente.
O modo documentado anterior continua por omissão nos livros existentes. Limite/suporte manuais ficam conservados quando se escolhe derivação, mas não são utilizados como limite activo nesse modo. Nova correspondência fica ligada à chave da colecta L6; origem alterada exige nova conferência. Método inválido é recusado. Os resultados antigos congelados não são reescritos. Convenção/aplicabilidade/limite continuam documentados por caso; efeitos L7 e outros regimes/períodos/moedas conservam correspondência própria. Não há consumo de crédito oficial, pagamento ou submissão fiscal.
Percurso demonstrado: cálculo → gravação/actualização/GET pelos endpoints privados existentes → recuperação com snapshot íntegro → recálculo corrente → HTML e relatório financeiro do dossier. Exemplo exclusivamente sintético:base4400/colecta1408/parcela100 produz fracção32;imposto estrangeiro100 limitado32,PEC200/PPC500 deixam676MZN de saldo simulado. Parcela0,01 produz fracção exacta2/625,sem arredondamento fiscal presumido. Correspondência em falta deixa resultado indeterminado. LimiteCDT6 continua a reduzir a dedução e a produzir702 no cenário documentado. Provas em `outputs/irpc-national-fraction-20261110/recovered-reports.json`,HTMLs/relatórios no mesmo directório.
Seis casos frontend novos (cinco iniciais e um adicional de afectação por países),um caso API/23 asserções apenas do delta, com transacção revertida na QA existente `proactive_investment_20261027_test:55441`,actor sintético existente1021. Sem novas bases,concessões,migrations ou alterações aos documentos/históricos anteriores. Tipos e lint dirigidos aprovados; compilação QA final acompanhada no log próprio. Provas aceites de IVA/IRPC/PEC/transporte/económico não repetidas. Reprodução dirigida: `node --test tests/frontend/irpc-national-fraction.test.mjs`; gerador de amostras `tools/irpc-national-fraction-fixtures.mjs`; writer QA `php tools/irpc-national-fraction-qa.php test`; conferência dos relatórios recuperados `node tools/irpc-national-fraction-recovered.mjs`.
**L8 continua parcial; fiscal não se limita a L8.** Transporte ordinário e crédito económico já entregues em569a1cd7 preservados. Resíduos L1–L7 e lotes L9–L16 estão explicitados no saldo por módulo existente, sem presumir fecho integral a partir de uma entrada ou taxa. Novo delta L8 e continuidade de Educação são compatíveis (sem schema/rotas/writers novos), agrupados em `outputs/entrega-educacao-l8-20261110.zip`, com manifesto/fontes/patch/reversão contra ff726e57; ainda não instalados. Tipos/lint/Pint/compilação QA final aprovados. O índice de trabalho e o pacote anterior ficam preservados; conferência em índices temporários verifica aplicação e reversão, sem instalar ficheiros. Homologação mantém ff726e57; conferência visual permanece separada. O próximo trabalho é outra pendência funcional do saldo, não repetir esta prova.
## 10/11/2026 — execução de conclusão integral após instalação ff726e57
Entrega de fontes até `ff726e57` instalada e verificada por HTTPS na homologação; inclui S08 e os restantes ajustes do pacote selado. Disponibilidade de código não é uma nova atribuição real nem prova de todos os percursos privados. Recibo `outputs/reviewed-install-20261110/verification.json`: revisão servida, 44 hashes do delta, configuração preservada, zero migrations/importações e release anterior conservada. As anotações antigas «sem instalação» continuam históricas, superadas neste âmbito. Conferência visual S08 permanece no registo próprio, sem reinstalação ou bloqueio transversal.
Saldo actual por módulo/percurso reconciliado na visão consolidada existente e em 35 IDs da matriz, sem criar novas linhas ou apagar estados/critérios anteriores. Os 3.555 registos incluem cláusulas/campos sobrepostos, não 3.555 funcionalidades independentes. Restantes células preservadas; recibos aceites reutilizados, sem nova auditoria geral.
**Utilização avançada nesta passagem — Educação:** no detalhe privado de uma declaração, preparar o fundamento da revisão e consultar Anterior/Seguinte no histórico deixa de exigir abandonar a revisão. As ligações conservam estado/posição; resposta nova actualiza a declaração mas não adopta silenciosamente a versão analisada. Um envio sem confirmação continua recuperável com a chave original. Alterar declaração/tipo/actor mantém a chave de isolamento do componente. Sair para outra página continua protegido pela guarda existente. Não houve novo endpoint, decisão automática, writer ou efeito na base.
Quatro verificações novas de comportamento em `tests/frontend/education-review.test.mjs`, seleccionadas por `history paging`, aprovadas. Tipos globais, lint dirigido e compilação QA concluídos com código 0. O build QA existente foi actualizado, sem novo ambiente ou dados. Logs `storage/logs/education-history-continuity-20261110-{types,build}.log`. Sem repetir testes aceites ou suites PHP. O delta é local e ainda não instalado remotamente; não há prova de navegador nem persistência durável do motivo entre sessões. Saldo no módulo: essa retoma durável, jornadas externas com representação/vínculos documentados e conferência visual. O proprietário já indicou que não existe cadastro/documento aprovado desses vínculos; isso bloqueia apenas os ramos externos dependentes.
Próximo trabalho funcional na ordem preservada: saldo fiscal L8 com fontes aceites, seguido de modalidades de Imobiliário/Bens de consumo/franquias e Estatística/IA, sem reabrir os fechos aceites. Não há exclusão de funcionalidades previstas nem conclusão global presumida.
## 10/11/2026 — preparação conjunta de contador e aliases
[Entrega conjunta](../v2/auditoria-portal/entrega-conjunta-contador-aliases-2026-11-10.md) preparada contra `main`/`origin/main` em `2209b838`, com patch único, reversão e manifesto. Oito casos novos/56 asserções conferem contexto sem base de dados nos dois aliases, GET/HEAD e com/sem barra final. Hashes das fontes e do build privado do contador continuam iguais ao recibo aceite; provas anteriores reutilizadas, não repetidas.
Permanecem separados: **instalação conjunta pendente**, **revisão visual pendente** e **melhorias da janela S08 por concluir**. Contexto preservado no redireccionamento não demonstra a funcionalidade da S08 nem encerra a revisão visual. Sem instalação, publicação ou alteração de dados/configurações.
## 10/11/2026 — contagem única e cobertura multiterritorial preparadas
O total conta cada oportunidade pública uma única vez, mesmo com vários territórios. O indicador de províncias conta localizações declaradas distintas: uma oportunidade em duas províncias corresponde a uma no total e duas na cobertura. Localização desconhecida mantém a oportunidade no total sem inventar província. Abrangência nacional e contexto de corredor não se convertem automaticamente em implantação.
[Código, critérios e provas](../v2/auditoria-portal/revisao-externa-2026-11-10.md): nove testes novos/23 asserções passaram; cache versionada, tipos, lint e compilação privada verificados. **O contador e os aliases anteriores estão preparados/testados, sem disponibilização no portal. Revisão visual e melhorias da janela S08 continuam pendentes, com provas aceites preservadas.** Nenhuma prova histórica do simulador/Balcão/aliases/S08 foi repetida ou tomada como conclusão nova. Sem dados de negócio alterados, migrations ou deploy.
## 10/11/2026 — entradas antigas do simulador e dos contactos
Os endereços `/simulador` e `/contactos`, também com barra final, passam a redireccionar permanentemente para `/simulador-fiscal` e `/apoio`. Os parâmetros de consulta são conservados; os formulários e permissões dos destinos existentes mantêm-se. O alias anterior `/Simulador` permanece disponível.
[Relatório da revisão externa](../v2/auditoria-portal/revisao-externa-2026-11-10.md): nove testes dirigidos/37 asserções passaram sem acesso à base ou email real. O teste do router não é uma demonstração visual ou HTTP ponta a ponta. A contagem multiterritorial e a revisão desktop/móvel continuam pendentes; não se alteraram dados nem regras fiscais. Reversão limitada ao ciclo de aliases em `routes/web.php`, sem rollback de schema.
## 02/11/2026 - reconciliacao das pre-condicoes da suite ampla
A classificacao das falhas foi confrontada com os testes e com a configuracao efectiva. Os quatro 403 do intake de negocios pertenciam ao percurso de listas legadas, mas herdavam a activacao de perfis do ambiente. Com variante legada explicita, passaram nove testes/248 assercoes do intake e das operacoes documentais reais; GD/WebP foi activado apenas pelo argumento PHP -d extension=gd, sem alterar o runtime partilhado. O proprio ExistingBusinessIntakeTest passou a declarar essa variante no setUp, conservando todas as recusas de isolamento, MFA, designacao, auto-aprovacao e estados. Verificacao sem flags externas de perfis: sete testes/154 assercoes passaram. Nenhuma politica do produto foi alargada.
@@ -7620,6 +8070,7 @@ Cobertura: identificação/período, 27 campos manuais IVA e 24 campos manuais M
Pedido expresso da Tempo: preencher formulário de IVA ou IRPC, em vez de abrir legislação. Base preservada: entrega instalada 789fffd0, Modelo A e aquisições/apuramento já aceites, L6 M20/M22 instalado em 5aaf1a56 e incrementos de L8. Nenhuma perda confirmada neste recorte; não se apagam as aceitações nem se repetem provas fiscais anteriores. Fontes reutilizadas do inventário de 20 exemplares acessíveis em outputs/irpc-l6-values-forms/accessible-source-inventory.json da QA anterior, incluindo temporários identificados; inventário do corpus e originais preservados. M22: três páginas conferidas visualmente nesta revisão, SHA256 4b61bd83d1fbcfd3b7b5c5523cea5923f7d0f65dc4c004eff58e5db38f763c88. Modelo A: correspondência e divergências aceites em modelo-a-reconciliacao-20261021.md; sem nova validação de proveniência.
Incremento em preparação QA: entrada directa em Documentos e Formulários, preenchimento manual dos campos correspondidos de IVA e subconjunto M22, identificação/período, revisão, impressão e ficheiro privado para recuperação mediante confirmação. Escolha técnica: sem persistência no servidor nem ligação automática ao dossier neste incremento; não altera schema, dados, permissões ou cálculos. Ficheiro não publicado. IRPC ajustamentos completos, outros regimes, anexos, assinatura e submissão à AT fora da cobertura; L8 permanece parcial. Ainda sem instalação deste incremento. Próximas provas apenas do delta: fidelidade da recuperação, rejeição de ficheiros incompatíveis sem perda, formatos/valores desconhecidos, acesso público e compilação.
<<<<<<< HEAD
@@ -9080,3 +9531,118 @@ Pedido expresso da Tempo: resolver os conflitos de `codex/investemoz-reconciliat
O catálogo conserva o layout lateral/responsivo e os filtros expansíveis, a selecção validada com recuperação de falha do armazenamento, as acções de limpar nos corredores e na lista, os resultados relacionados distintos da pesquisa, a paginação bloqueada durante pesquisa/falha e o mapa único com camadas e limites administrativos pelo território aplicado. Não atribui localização distrital nem coordenadas a uma indicação provincial. Manual e matriz conservam os registos de ambos os lados; matriz com 3.555 requisitos únicos e 49 colunas. As provas aceites e pendências visuais mantêm-se; esta resolução não constitui aceitação visual ou disponibilização activa.
Verificação do delta: 25 testes frontend dirigidos aprovados; TypeScript e compilação QA aprovados; lint dos dois ficheiros React sem erros após normalizar o título do sector; pesquisa de marcadores e `git diff --check` limpos. Compilação apenas em `public/build-qa-opportunities`, com aviso de dimensão de chunks. Sem alterações de schema, base de dados, permissões, serviços ou outros worktrees. Recibos locais: `outputs/reconciliation-conflicts-20261104/`.
### 04/11/2026 — integração da reconciliação na main exclusivamente local
Pedido expresso da Tempo: integrar a reconciliação na `main` local, preservar as alterações, verificar conflitos e testes, sem enviar para o remoto. A `main` anterior (`4b3c7e16`) foi preservada em `backup/main-before-reconciliation-20261104`. A integração de `codex/investemoz-reconciliation-20261029` (`f17e807c`) fez fast-forward: a main anterior é antepassada da origem. Não houve nova resolução automática nem descarte de conteúdo; a árvore integrada foi conferida idêntica à origem. Os oito conflitos gravados já estavam resolvidos no commit de origem e não existem marcadores em app/resources/routes/tests/docs. Sem alterações por gravar anteriores ou alterações nos outros worktrees.
Verificação local: 1.672 testes frontend aprovados, zero falhas; TypeScript global aprovado; compilação QA aprovada em `public/build-qa-opportunities` (avisos de fontaine opcional e dimensão dos chunks); sintaxe dos 468 ficheiros PHP alterados aprovada; `git diff --check` limpo. Matriz preservada com 3.555 requisitos únicos e 49 colunas. Recibos e logs: `outputs/reconciliation-main-local-20261104/integrity.json`. Testes dependentes de PostgreSQL não reexecutados: não havia listener da QA isolada em 55441. Provas anteriores preservadas, sem as apresentar como nova execução. Esta integração não declara aceitação funcional/visual integral, disponibilização no portal nem execução de migrações. Sem push, escrita na base activa, publicação de conteúdos ou alteração de serviços/permissões.
### 04/11/2026 — base QA exclusiva e testes da main local
Pedido expresso da Tempo: preparar QA isolada e executar os testes de base de dados da main local, sem tocar na base activa nem enviar alterações ao remoto. Base de código `66a5dbf3`, após integrar a reconciliação. Criado cluster PostgreSQL/PostGIS novo em directório exclusivo `C:/Users/macbook/.config/investmoz/main-qa-20261104-pgdata`, apenas em `127.0.0.1:55441`, com cinco bases terminadas em `_test`. Guardas conferem nome, porta e directório real antes de migrações/testes; sem configuração em cache, DB_URL ou fallback activo. InvesteMoz, SGF e os restantes clusters não foram reiniciados nem reconfigurados. QA permanece disponível; nenhuma instalação de serviço ou alteração global do PHP.
Resultado final por caso distinto, substituindo o resultado inicial apenas quando houve repetição documentada: **1.325 aprovados, 28.377 asserções, zero falhas por resolver nesta ronda**. Todos os 274 ficheiros Feature cobertos, mais 94 casos unitários e ciclo de schema com 3.106 asserções. Inclui Competitividade 7/93 e filtros provinciais 4/364. A ronda principal inicial teve 877 casos, 873 aprovados, duas falhas e dois erros; os quatro casos passaram em repetição dirigida após carregar GD/WebP apenas no processo QA e corrigir uma expectativa antiga de auditoria. O teste conserva os 13 eventos de negócio, verifica os oito eventos já existentes de início/resultado de quatro scans e confirma ausência de duplicação no replay. Não se alterou o código da aplicação.
Preparação e diagnósticos preservados: dois snapshots QA anteriores restaurados em bases separadas e actualizados pelas migrações locais; histórico de uma migração da oferta reconciliado só no clone, após comparar colunas, constraints, índices, políticas e RLS com o schema actual. Dois testes com traits combinados de RefreshDatabase tinham sido mal agrupados: executor da cópia de referência interrompido, logs conservados, cópia restaurada e testes separados; não foi perda de dados activos. Fixture fiscal própria executada em schema limpo, sem apagar versões legítimas do snapshot. Chave local compatível do snapshot verificada e disponibilizada apenas ao executor como chave anterior, sem expor, alterar chave ou reescrever registos. Originais editoriais existentes copiados para a fixture QA com hashes verificados. Falhas iniciais não são apagadas nem apresentadas como regressões resolvidas no produto sem causa demonstrada.
Preservação: 523 JSON históricos conferidos/restaurados por SHA256, 17 saídas novas arquivadas separadamente; dumps originais QA inalterados. Matriz mantém 3.555 requisitos únicos e 49 colunas. Pint, sintaxe do teste alterado e diff-check aprovados. Sem push, publicação, migração/escrita activa ou alteração de permissões activas. Estas provas não substituem a conferência visual autenticada nem encerram requisitos transversais; pendências anteriores conservadas. Recibo: `docs/investmoz/unification/main-local-database-qa-20261104.json`; resultados por teste, tentativas iniciais, executores e JUnit em `outputs/main-db-qa-20261104/`.
### 04/11/2026 — conferência visual da main na QA isolada e correcção do CSV provincial
Percurso executado no Edge em `http://127.0.0.2:18644`, base exclusiva `main_visual_20261104_test` no cluster QA `127.0.0.1:55441`, com actor e conteúdos sintéticos. Autenticação Fortify normal com MFA: editar, guardar v2, recarregar e conferir os valores; aprovar v3; guardar novo rascunho v4; terminar sessão e conferir publicamente apenas v3, sem o marcador do rascunho. Histórico v1–v4 preservado. Esta prova conclui o recorte visual local de Competitividade; não equivale a execução no portal remoto ou aceitação da equipa em dados reais.
Filtro apenas por Maputo Província, com sector Agronegócio: cinco oportunidades existentes mais quatro fixtures sintéticas nos níveis província/distrito/posto/localidade resultaram em nove na grelha, lista, mapa e CSV. Distrito de Matola apresentou três; regressar a toda a província recuperou nove e limpou distrito/posto/localidade. Nenhuma coordenada criada: mapa informa zero locais e nove sem coordenadas. Hash das oportunidades anteriores idêntico antes/depois. Limite administrativo desta província indisponível na QA, explicitamente indicado; cobertura cartográfica transversal continua pendente.
Detectada e corrigida divergência no CSV: coluna Província vazia apesar da localização canónica/contexto de corredor disponível. Exportação reutiliza a apresentação territorial existente, conserva as oito primeiras colunas e o escape contra fórmulas, acrescentando Localização e âmbito. Contexto do corredor indica expressamente que distrito/posto não foram indicados; não grava nem inventa localização. CSV descarregado pela interface após correcção: nove linhas, todas com província e âmbito coerentes.
Delta aprovado: 17 testes de base de dados, 609 asserções; PHPStan sem erros e Pint aprovado. Provas anteriores de 1.325 testes/28.377 asserções preservadas, sem repetição integral. Recibo com hashes e capturas: `docs/investmoz/unification/main-local-visual-qa-20261104.json`; artefactos em `outputs/main-visual-qa-20261104/`. Apenas main local e QA; sem push, escrita activa ou alteração de permissões activas. Conferência dos filtros no portal de destino e pendências gerais ACT-02/V2-07/V2-10 continuam abertas.
### 04/11/2026 — ACT-20261029-02: limites do nível imediatamente inferior e lacunas de cobertura
Continuação sequencial na main local sobre `6dde36b5`, reutilizando V2-07/V2-10, serviço de geometria, permissões e mapa único. Inventário prévio confirmou camadas/legenda/fontes já existentes e contexto limitado à selecção/antepassados; nenhuma perda confirmada. Os 12 artefactos do recibo visual anterior foram novamente conferidos por SHA256, sem alteração. Provas aceites de 1.325 testes e demais incrementos preservadas, sem repetição integral.
O catálogo solicita `include_children=1` no endpoint administrativo existente. A opção acrescenta apenas referências do nível imediatamente inferior: distritos de uma província, postos de um distrito e localidades de um posto. Sem a opção conserva o âmbito anterior. Geometrias continuam sujeitas à classificação, acesso efectivo e snapshots/hashes de divulgação existentes; não há publicação, importação ou nova autorização. O território seleccionado tem prioridade, seguido dos antepassados e subdivisões. Até 100 geometrias e 500 referências inferiores, com truncagem explícita.
Resposta acrescenta `child_territory` e `child_coverage` com nível, referências acessíveis e territórios representados, distinguindo contagem territorial de polígonos. Interface identifica subdivisão/contexto/território seleccionado, conserva fonte/edição e explicita que falta de limite na consulta não significa inexistência do território, nem terreno disponível. Nenhuma oportunidade é reposicionada e nenhuma coordenada é presumida.
Delta aprovado: 11 testes de base de dados/125 asserções (cinco novos e seis de acesso existente), nove testes frontend, PHPStan sem erros, tipos, Pint, lint e build QA aprovados. Inclui exclusão de outra província/netos, limites públicos/internos, referência eliminada, ausência de geometria, priorização e truncagem de geometrias/referências, validação do parâmetro e descarte de resposta anterior ao mudar a opção. Primeira tentativa da fixture falhou por faltar país-pai; corrigida a fixture, mantendo a integridade territorial e os logs iniciais.
Conferência visual no Edge, exclusivamente QA `127.0.0.2:18644`/`main_visual_20261104_test`: Maputo Província mostra zero limites de oito referências distritais acessíveis, lacuna explicitada, nove oportunidades mantidas sem ponto inventado. O snapshot não contém limites públicos para demonstrar visualmente os polígonos inferiores; estes foram exercitados por testes sintéticos. Recibo/hashes: `docs/investmoz/unification/map-subdivision-local-20261104.json`; captura em `outputs/map-subdivision-20261104/`.
**ACT-02 permanece parcial.** Camadas factuais de recursos, infra-estruturas, mão-de-obra e potencial económico, cobertura de dados/geometrias e conferência no portal de destino continuam pendentes. Este recorte não encerra V2-07/V2-10 nem substitui aceitação da equipa. Sem push, migração, escrita activa, importação, alteração de permissões ou reinício de serviços.
### 04/11/2026 — ACT-20261029-02: camadas de evidência pública de recursos e infra-estruturas
Incremento sequencial na main local sobre `bbc05f1d`, reutilizando V2-07/V2-10, mapa único, filtros, pontos/áreas públicos e contexto administrativo já demonstrados. Inventário de código e cópia QA antes de fixtures: 48 oportunidades publicadas e duas sínteses proactivas públicas, mas nenhuma evidência pública local das categorias resource/infrastructure. Os campos existentes de zonas e fontes editoriais não foram convertidos automaticamente em disponibilidade ou geometrias temáticas; evidências privadas não foram publicadas. Não é perda confirmada de dados nem diagnóstico do remoto. Conferidos por SHA256 os 23 artefactos dos dois recibos visuais/administrativos anteriores, todos inalterados.
A API existente de mapa acrescenta `thematic_records` a partir de `ProactiveInvestment::publicData`: somente evidência seleccionada na síntese publicada, categorias recursos/infra-estruturas e propósito local. Conserva facto, fonte, localizador, período, âmbito, cobertura, verificação registada, situação e limitações. Exclui metodologia e outras categorias; não copia o rascunho privado. Mantém a consulta territorial, limite/truncagem e autorização pública anteriores. As áreas acrescentam o identificador da oportunidade para filtragem, sem atribuir a geometria à evidência.
No mesmo mapa, o selector permite todas as oportunidades, recursos documentados ou infra-estruturas documentadas. A camada filtra apenas pontos/áreas existentes das fichas correspondentes; não alarga a pesquisa geral, não altera CSV nem cria posições. Fichas sem ponto continuam consultáveis no painel de evidências/fontes. Contagens referem fichas, não quantidade/capacidade de recursos. Âmbito da evidência distingue-se da localização da oportunidade; marcadores não representam posição exacta ou disponibilidade de recurso/infra-estrutura. Escolha recuperada na sessão do navegador, com tolerância a armazenamento indisponível/inválido. Falta de evidência, período ou fonte é explícita, sem zero presumido.
Delta aprovado: oito testes de BD/438 asserções (quatro novos e quatro de filtros provinciais), 14 testes frontend (cinco novos e nove existentes), PHPStan sem erros, tipos, Pint, lint e build QA aprovados. Inclui exclusão de rascunhos/publicação futura/acesso restrito, leitura apenas da síntese pública, fonte/período/limites, província/distrito, reutilização das coordenadas existentes, alternância/reposição de camadas, fichas sem ponto, carregamento/ausência/truncagem, escape de texto e recuperação da escolha. Provas gerais anteriores preservadas, sem repetir a bateria completa.
Conferência visual no Edge exclusivamente em `127.0.0.2:18644`, base `main_visual_20261104_test`: duas fixtures claramente sintéticas criadas apenas em QA, uma por categoria; nenhuma coordenada/geometria criada. Hash de todas as oportunidades anteriores idêntico antes/depois. Alternância entre ambas conserva pesquisa provincial e duas fichas gerais; cada camada mostra a sua ficha, fonte/período/âmbito/limites e ausência de ponto. Infra-estruturas recuperada após recarregar. Recibo: `docs/investmoz/unification/thematic-map-local-20261104.json`; inventário, hashes, DOM e capturas em `outputs/thematic-map-20261104/`. Demonstração sintética não constitui cobertura económica factual ou publicação de dados reais.
**ACT-02 permanece parcial.** Cobertura factual de recursos/infra-estruturas no destino, georreferenciação própria quando documentada, mão-de-obra, potencial económico quantitativo, restantes fontes temáticas e lacunas cartográficas continuam pendentes. Não se inferem estas camadas de descrições nem do número de oportunidades. Mantêm-se conferência remota e aceitação da equipa; V2-07/V2-10 não encerrados. Sem push, alteração activa, migração, publicação automática ou alteração de permissões/serviços.
### 04/11/2026 — ACT-20261029-02: mão-de-obra documentada e contexto económico
Incremento qualitativo na main local sobre `21ebad73`, reutilizando mapa, evidências, síntese publicada, divulgação expressa e fontes/períodos existentes. Inventário na cópia QA: duas evidências públicas locais de actividade anteriores (fichas sintéticas 350/383), nenhuma de mão-de-obra/oferta/procura; recursos/infra-estruturas da demonstração anterior conservados. As declarações labour_demands/training_capacities têm acesso EducationAccess interno; emprego/indicadores financeiros canónicos não fazem parte da publicProjection. Observações de zonas conservam divulgação/unidades/metodologia próprias. Não foram expostas nem agregadas essas fontes privadas por existir revisão interna. As fontes oficiais aceites e as decisões de divulgação anteriores não foram reabertas.
Acrescentada categoria explícita Mão-de-obra no editor e validação da evidência existente, sem migração ou reclassificação automática dos textos anteriores. Guardar, recuperar, pré-visualizar e confirmar publicação usam o percurso existente. A camada económica reutiliza apenas evidências públicas locais de actividade/oferta/procura, mantendo `source_category` original, facto, fonte, localizador, período, verificação, âmbito, cobertura e limitações. Agrupá-las para consulta é uma escolha de apresentação, não aprovação de viabilidade nem índice de potencial.
Ambas as opções entram no selector do mapa único; a escolha é recuperada na sessão. Recursos/infra-estruturas, geometrias públicas, filtros territoriais, fichas sem ponto e contagens anteriores mantêm-se. Nenhuma posição é criada ou atribuída ao recurso/mão-de-obra. Necessidade ou qualificação não equivale a trabalhador disponível; previsão de emprego não é emprego efectivo. Contexto económico não calcula retorno, score ou viabilidade. Períodos/fontes incompatíveis permanecem separados, sem totais ou zero presumido.
Delta aprovado: sete testes de BD/77 asserções (cinco do mapa, incluindo o novo caso; dois de publicação, um novo e um de acesso/fonte privada já existente), 22 testes frontend, PHPStan sem erros, tipos, Pint, lint e build QA aprovados. Teste do percurso: criar evidência workforce → recuperar → confirmar pela publicação existente → consultar mapa → alterar rascunho privado sem substituir a categoria/fonte públicas. Testes mantêm metodologia/internos fora do mapa, categorias originais, períodos distintos e ausência de quantidades/índices inferidos. Não se repetiu a bateria geral aceite.
Conferência visual no Edge exclusivamente na QA `127.0.0.2:18644`/`main_visual_20261104_test`: contexto económico reutiliza a evidência de actividade já publicada da ficha 383, sintética e explicitamente identificada. Mão-de-obra usa uma única fixture nova, também sintética; escolha e fonte recuperadas após recarregar. Nenhuma coordenada/geometria criada; hash de todas as oportunidades anteriores idêntico antes/depois. Conferidos 36 artefactos dos três recibos anteriores, todos com SHA256 inalterado. Recibo: `docs/investmoz/unification/workforce-economic-map-local-20261104.json`; inventário, DOM, capturas e hashes em `outputs/workforce-economic-map-20261104/`.
**ACT-02 permanece parcial.** Cobertura factual laboral/económica, séries, unidades e metodologia quantitativa comparável, georreferenciação própria documentada e cobertura cartográfica continuam pendentes. A demonstração sintética não mede oferta laboral nem potencial regional real. Mantêm-se conferência remota e aceitação da equipa; V2-07/V2-10 não encerrados. Sem push, migração, publicação automática, escrita activa, alteração de permissões ou reinício de serviços.
### 04/11/2026 — ACT-20261029-02: observações quantitativas públicas de emprego e contexto económico
Incremento na main local sobre `a9241ff2`. Inventário prévio: fichas de parques já conservam observações com valor, unidade, período, âmbito, população/base, metodologia, conhecimento e divulgação seleccionada. Reutilizada apenas a versão pública de `operational_details.public.integral.observations`, sem desencriptar preparação privada. Na cópia QA havia oito fichas públicas e zero observações quantitativas divulgadas; não foi declarada perda nem ausência no remoto. As 51 provas anteriores foram conferidas por SHA256 antes da ronda e novamente no fim, todas inalteradas; bateria geral aceite preservada sem repetição integral.
A API de mapa acrescenta `quantitative_context` a partir do mesmo conjunto de zonas já devolvido no contexto territorial: emprego para mão-de-obra; investimento, produção, exportações e compras locais para contexto económico. Reutiliza os filtros/autorização existentes, sem alargar a pesquisa de oportunidades nem transformar indicadores do parque em indicadores das oportunidades. Filtros específicos que já excluem esse conjunto devolvem âmbito não incluído, em vez de uma falsa medição vazia. Até 30 fichas e 200 observações; truncagem explícita. Geometrias, divulgação, fontes e versões anteriores conservadas.
O painel do mapa mostra cada valor com a sua unidade, período, âmbito, população/base, metodologia, fonte/data, versão pública e ligação à ficha. Zero documentado e valor desconhecido são distintos; precisão decimal conserva-se como texto. Divergências e metadados em falta permanecem visíveis. Emprego não é oferta de trabalhadores disponíveis; natureza prevista/efectiva depende da descrição da fonte. Não se somam populações ou períodos, não se convertem unidades/moedas e não se criam médias, índices ou previsões de retorno. Contagens de oportunidades e de observações territoriais têm âmbitos distintos. Erro, carregamento, cadastro indisponível, âmbito não consultado e ausência de observações são estados diferentes.
Verificação dirigida aprovada: 11 testes BD/99 asserções (seis novos e cinco do mapa temático existente), 18 testes frontend (três novos e 15 existentes), PHPStan sem erros, tipos, Pint, lint e compilação QA aprovados. Inclui zero/decimais, desconhecido/divergência, metadados incompletos, exclusão de preparação privada/ficha não publicada, preservação do envelope, província/distrito, limites de observações/fichas e âmbito excluído. Primeira tentativa teve uma expectativa inadequada sobre a serialização JSON antes da normalização jsonb; comparação corrigida para duas leituras efectivas do servidor. Fixture frontend com rótulo repetido corrigida; não se relaxaram regras do produto. Logs BD iniciais mantidos.
Conferência visual exclusivamente em `127.0.0.2:18644`/`main_visual_20261104_test`: uma ficha sintética nova com cinco observações. Camada laboral mostra zero previsto e emprego indirecto por determinar, em duas linhas; económica mostra 125.7500 toneladas, 100000.00 MZN e 25.50 USD em divergência, em três linhas, com as respectivas fontes e períodos. Valores exclusivamente de teste, sem aplicação económica real. Nenhuma coordenada/geometria criada; hash de todas as fichas territoriais anteriores idêntico antes/depois. Recibo e hashes: `docs/investmoz/unification/quantitative-map-local-20261104.json`; inventário, capturas e DOM em `outputs/quantitative-map-20261104/`.
**ACT-02 permanece parcial.** Cobertura factual nacional/regional, séries comparáveis, oferta laboral, natureza das grandezas e metodologia quantitativa de potencial económico continuam pendentes conforme dados aplicáveis. A demonstração sintética não é estatística real, índice regional ou diagnóstico do portal remoto. Mantêm-se conferência remota e aceitação da equipa; V2-07/V2-10 não encerrados. Sem push, instalação, escrita activa, migração, publicação automática ou alteração de permissões/serviços.
Precisão adicional demonstrada: número JSON `1234567890123.456789` recuperado sem arredondamento. O valor é extraído como texto decimal do mesmo snapshot público carregado, sem o converter num float PHP nem misturar metadados e versão com uma leitura posterior. O teste dirigido de precisão e PHPStan passaram após esta correcção.
### 04/11/2026 — ACT-20261029-02: filtros de indicador e período das observações quantitativas
Main local sobre `3f50f666`, sem push. Inventário do concluído: mantidas as camadas temáticas, hierarquia territorial, observações públicas, precisão decimal, fonte/data/versão, estados de conhecimento e limites de cobertura já demonstrados. Conferidos por SHA256 os 63 recibos JSON anteriores da pasta de unificação, todos inalterados. Não foi identificada perda neste delta; as provas aceites, incluindo 1.325 testes/28.377 asserções, não foram reabertas. A instalação anterior de `3f50f666` mantém recibo em `outputs/main-install-20261104/installation-result.json`; os estados «sem instalação» das entradas anteriores descrevem a sua execução histórica.
Próximo recorte dos filtros previstos na matriz: selecção por indicador e período de referência sobre as observações quantitativas públicas já recebidas no mapa. Sobreposição temporal inclusiva, com início e/ou fim opcionais; intervalo invertido apresenta erro. Períodos incompletos, inválidos ou invertidos da fonte continuam consultáveis sem filtro temporal e são excluídos quando este é aplicado; a data da fonte não substitui o período. A limpeza repõe as observações. Não altera a pesquisa de oportunidades, os limites de resposta ou a cobertura territorial; não soma, converte, estima nem infere comparabilidade. Escolha de implementação delimitada aos filtros da ACT-02, sem nova regra organizacional.
Delta verificado: 12 testes frontend dirigidos aprovados, tipos globais, lint do componente e build QA aprovados. Conferência visual apenas em `127.0.0.2:18644`/`main_visual_20261104_test`, reutilizando as cinco observações sintéticas anteriores, sem novas fixtures ou escrita de dados. Produção em 31/12/2025 devolve uma linha com `125.7500` toneladas; intervalo invertido mostra erro; limpar repõe cinco linhas. As 12 oportunidades do cenário QA mantêm-se. Corrigida e conferida a actualização dos campos de data durante a introdução. Recibo e hashes: `docs/investmoz/unification/quantitative-map-filters-local-20261104.json`; DOM, captura e logs em `outputs/quantitative-filters-20261104/`.
**ACT-02 permanece parcial.** Cobertura factual nacional/regional, séries comparáveis, oferta laboral e metodologia quantitativa de potencial económico continuam pendentes conforme fontes aplicáveis. O cenário sintético não é estatística real. Conferência autenticada de Competitividade pela equipa e requisitos amplos V2-07/V2-10 preservados. Sem push, instalação deste delta, migração, importação, publicação automática, escrita activa, alteração de permissões ou reinício de serviços.
=======
>>>>>>> 705dc69d683a451eda1371db7eceb280047b2895
### 10/11/2026 ? commits separados; S08 e visual pendentes
Ajustes locais separados por ?mbito na branch `entrega/directiva-20261110`: pacote contador/aliases `377c40a2`; contactos `4e614802`; informa??o/SEO `4e07557c`; pr?-dossier `dcecd251`; curadoria `7f131497`; sitemap `252f38bb`. Cobertura, limites e hashes em `docs/investmoz/v2/auditoria-portal/ajustes-por-ambito-2026-11-10.md`. Provas aceites preservadas. Sem push ou instala??o.
S08 permanece por concluir, separada da revis?o visual; a especifica??o da janela n?o foi recuperada e n?o ? substitu?da pela etapa de licenciamento hom?nima. Visual interrompido por impossibilidade da ferramenta de confirmar o URL, n?o por Escape nem por falha demonstrada do portal. Cluster QA 55441 mantido ligado enquanto houver depend?ncias por conferir. Nenhum requisito amplo ? promovido a conclu?do por estes commits.
### 10/11/2026 ? S08: melhoria do percurso existente de licenciamento pr?vio
?mbito esclarecido pela Tempo; substitui o anterior saldo ?por identificar?. Implementa??o e sete provas frontend dirigidas em `67344418`: estado sem repeti??o, registo/ac??o principal vis?veis, redu??o dos blocos expans?veis e abertura/foco da etapa por `#S08`, preservando campos/contexto/vers?o/grava??o e guardas existentes. TypeScript, lint dirigido e build QA aprovados. Sem altera??o de backend/hist?rico/dados ou repeti??o das provas aceites n?o afectadas. Detalhes e limites em `docs/investmoz/v2/auditoria-portal/ajustes-por-ambito-2026-11-10.md`. Revis?o visual e instala??o mant?m-se pendentes e separadas. Pacote contador/aliases preservado; sem push e cluster 55441 mantido ligado.
@@ -0,0 +1,168 @@
{
"date": "2026-11-04",
"base_commit": "21ebad73",
"requirement": "ACT-20261029-02; V2-07/V2-10",
"scope": "Incremento qualitativo de mão-de-obra e contexto do potencial económico; main local",
"source_inventory": {
"database": "main_visual_20261104_test",
"public_local_evidence": {
"resource": 1,
"infrastructure": 1,
"workforce": 0,
"activity": 2,
"supply": 0,
"demand": 0
},
"private_sources_not_disclosed": [
"labour_demands/training_capacities: EducationAccess authorization",
"canonical employment and financial payloads: not part of publicProjection",
"zone observations: separate disclosure/units/methodology; not automatically mixed with opportunity evidence"
],
"existing_economic_sources": [
{
"opportunity_id": 383,
"title": "SYNTHETIC QA — ideia de apoio em Cahora Bassa",
"category": "activity",
"source": "SYNTHETIC QA exemplar público da interface",
"period": "2026 — cenário sintético",
"scope": "Cahora Bassa — referência distrital existente"
},
{
"opportunity_id": 350,
"title": "SYNTHETIC QA — ideia de apoio em Cahora Bassa",
"category": "activity",
"source": "SYNTHETIC QA exemplar público da interface",
"period": "2026 — cenário sintético",
"scope": "Cahora Bassa — referência distrital existente"
}
]
},
"implementation": [
"Categoria workforce no editor/validação existentes; mesmos campos de fonte, período, âmbito e divulgação expressa.",
"Mapeamento de activity/supply/demand já públicos para a camada economic_potential, conservando source_category e cada fonte/período.",
"Apenas evidência publicada, seleccionada e de propósito local; rascunhos privados e metodologia excluídos.",
"Escolha de ambas as novas camadas recuperável na sessão; pontos/áreas limitados aos locais existentes das oportunidades.",
"Sem unidades, quantidades, disponibilidade laboral, índices ou retorno calculados a partir dos textos; sem agregação de períodos/fontes incompatíveis."
],
"existing_private_sources": [
"EducationAccess protege labour_demands/training_capacities; revisão interna não é publicação.",
"OpportunityCanonicalContract::publicProjection não divulga emprego/indicadores financeiros privados.",
"Observações de zonas conservam divulgação, unidades e metodologia próprias; não foram automaticamente misturadas."
],
"contract": {
"route": "GET /api/v1/investment/map",
"field": "thematic_records.evidence",
"new_categories": [
"workforce",
"economic_potential"
],
"source_category": "categoria original explícita; recursos/infra-estruturas mantêm categoria anterior",
"version_compatibility": "Campos/fichas/históricos anteriores conservados; adição de categoria no rascunho existente, sem migração"
},
"checks": {
"database_tests": 7,
"database_assertions": 77,
"map_tests": 5,
"publication_tests": 2,
"frontend_tests": 22,
"phpstan_errors": 0,
"types": "passed",
"pint": "passed",
"lint": "passed",
"qa_build": "passed; avisos de fonte opcional e dimensão de chunks"
},
"visual": {
"url_base": "http://127.0.0.2:18644",
"database": "main_visual_20261104_test",
"economic_existing_opportunity_id": 383,
"economic_source_reused": "Evidência de actividade já pública, explicitamente sintética no snapshot QA; original preservado.",
"new_workforce_fixture_ids": [
534
],
"workforce_recovered_after_reload": true,
"new_coordinates": false,
"new_geometry": false,
"existing_opportunities_sha256": "90aaee1f9e47bb7793e18746fb1f6935bfb0a21329deca9c569707fadd2c04c6"
},
"preservation": {
"prior_artifact_hashes_verified_unchanged": 36,
"accepted_full_database_tests_preserved_not_reexecuted": 1325,
"previous_pending_items_retained": true
},
"pending": [
"Cobertura factual laboral e económica real, séries comparáveis, unidades e metodologia quantitativa permanecem pendentes.",
"A camada económica é contexto documental, sem classificação de viabilidade ou score regional.",
"Procura de trabalho, formação, empregos previstos e trabalhadores disponíveis não são intercambiáveis.",
"Conferência remota, aceitação da equipa e cobertura cartográfica permanecem pendentes; ACT-02/V2-07/V2-10 não encerrados."
],
"safety": {
"push": false,
"active_database_changed": false,
"migration": false,
"permissions_changed": false,
"automatic_publication": false,
"services_restarted": false
},
"proofs": {
"outputs/workforce-economic-map-20261104/source-inventory-before-fixtures.json": {
"sha256": "cd8a8943bf86778e041cc3f2ced51467d44e6e83147c8a8215024a27431d2964",
"bytes": 1313
},
"outputs/workforce-economic-map-20261104/fixture-receipt.json": {
"sha256": "7d680fed74a70d4367f1a7d8e0af60a0b7c5e5f89bc3ff4a17be5e119d294dc0",
"bytes": 405
},
"outputs/workforce-economic-map-20261104/existing-economic-context.jpg": {
"sha256": "6f81b29cbefad611b035fe26324d4b876d5dd5a9499a6fbd8ee75e8b4416aec5",
"bytes": 157445
},
"outputs/workforce-economic-map-20261104/existing-economic-context.dom.txt": {
"sha256": "1eb2687e9a3e73f4b3ed9808a9328930c54ca08dd6270e7d2e7bff1cfbef7227",
"bytes": 20535
},
"outputs/workforce-economic-map-20261104/workforce-reopened.jpg": {
"sha256": "b4c919ca4bfc344cd4feb5adcbe4a483d241d549d75450ee62a8b3854ccc6f34",
"bytes": 161166
},
"outputs/workforce-economic-map-20261104/workforce-reopened.dom.txt": {
"sha256": "01c6081409ea36d88e01d1c8b9af558eb497e68b596fe8be1b45cdec437c7515",
"bytes": 13566
},
"outputs/main-db-qa-20261104/workforce-economic-map.log": {
"sha256": "c6fb7cddb76a2589f3c41dc3d144b0387573417ddd9afcff96f331b801f53c0f",
"bytes": 94
},
"outputs/main-db-qa-20261104/workforce-economic-map.junit.xml": {
"sha256": "5824abb4aaa07c37e9420802d82a8ed08c4cd1babc76c337043016eb098f37ec",
"bytes": 2374
},
"outputs/main-db-qa-20261104/workforce-publication.log": {
"sha256": "3b0cb219fb68177a1a026ac7c076fe0da81f40c20aa6a8415a54866a08895c9d",
"bytes": 95
},
"outputs/main-db-qa-20261104/workforce-publication.junit.xml": {
"sha256": "7ad274166f6520990241a82530c51ac662540907bcc831c83889190176a80eaf",
"bytes": 958
},
"outputs/main-db-qa-20261104/workforce-economic-frontend.log": {
"sha256": "02dc96a1acd6887ac3fdc282562e8e5579e05bcf29283330effdf5689619ee09",
"bytes": 2811
},
"outputs/main-db-qa-20261104/workforce-economic-phpstan.log": {
"sha256": "1997495ca8f04c2ec09798d55607d1a2f85e3338b772f258666465bf658032d2",
"bytes": 49
},
"outputs/main-db-qa-20261104/workforce-economic-types.log": {
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"bytes": 0
},
"outputs/main-db-qa-20261104/workforce-economic-lint.log": {
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"bytes": 0
},
"outputs/main-db-qa-20261104/workforce-economic-build.log": {
"sha256": "ec0f15282f2dfe3c3c8dbe349773a88adbd87efba7e26332cd55c34b8f21bf25",
"bytes": 31606
}
}
}
@@ -0,0 +1,75 @@
# Ajustes separados por ?mbito ? 10/11/2026
Estado: commits locais na branch `entrega/directiva-20261110`, sobre main `2209b838`. Sem push, instala??o ou activa??o. A directiva permanece parcial. Provas anteriores conservadas; testes n?o equivalem a aceita??o ou revis?o visual.
| Commit | ?mbito | Cobertura dirigida j? executada | Pend?ncia |
| --- | --- | --- | --- |
| `377c40a2` | Contador e aliases: ?rvore exacta do pacote preparado | Contador/cache 9 testes/23 asser??es; aliases/contexto conforme recibo selado, incluindo 8 casos/56 asser??es de contexto | Instala??o e visual |
| `4e614802` | Operador Tempo, Lda. e contacto institucional existente | 2 testes frontend de apresenta??o/valor partilhado | Visual; nenhum contacto dedicado inventado |
| `4e07557c` | Resumo p?blico/SEO e data de verifica??o distinta de ausente/inv?lida | 2 testes frontend de informa??o | Visual; preenchimento factual continua dependente dos dados |
| `dcecd251` | Pr?-dossier imprim?vel a partir da projec??o autorizada existente | 3 testes/16 asser??es; 5 testes cliente de estado; QA 200 do titular/403 de outro actor, projec??o e registo preservados | Visual, impress?o/PDF real e percurso de equipa |
| `7f131497` | Destaque editorial expl?cito, pr?via, confirma??o, motivo cifrado e hist?rico paginado | 3 testes/21 asser??es de autoridade; 4 testes cliente; QA legado 11 verifica??es; publicador nativo/MFA; hist?rico 25+1 | Visual e instala??o; sem escolha/publica??o autom?tica |
| `252f38bb` | Sitemap p?blico, origem fixa e p?ginas limitadas | 4 testes/19 asser??es; QA compara 51 oportunidades com o scope p?blico | Visual n?o aplic?vel ao XML; publica??o/indexa??o e instala??o pendentes |
A ?rvore do primeiro commit ? `4c9334ffd750555a97fbd8ad29301a3954f74e76`, id?ntica ao manifesto do pacote. ZIP preservado: SHA256 `7aec8f2795dc44690fcc4cfb2c436db4d9b589d0112bd266e517b84074870213`. Os commits posteriores n?o alteram esse artefacto. O sitemap e a curadoria partilham apenas o ficheiro de rotas, com altera??es separadas nos respectivos commits.
## Limites das provas
Build final QA aprovado (2813 m?dulos, 37,56 s), PHPStan sem erros e TypeScript `tsc --noEmit` aprovado. Build QA privado: n?o foi instalado e n?o ? artefacto de produ??o. As provas anteriores s?o reutilizadas, sem repetir suites gerais.
QA exclusiva `main_visual_20261104_test`, porta 55441. Curadoria: transac??es revertidas; actores sint?ticos existentes, sem novas concess?es. Prepara??o MFA sint?tica revertida. Hist?rico 25+1 usa 26 notas sint?ticas, n?o decis?es de neg?cio. Registos/actor e contagem de auditoria restabelecidos; sequ?ncias de auditoria podem avan?ar apesar de rollback. Pr?-dossier n?o executa opera??es nem confirma elegibilidade fiscal.
A exist?ncia antiga de `is_featured` no editor legado n?o demonstrava disponibilidade na gest?o nova: o controlador legado bloqueia grava??o quando esta est? instalada. O commit de curadoria cobre essa lacuna sem alterar a autoridade nativa de publica??o, o estado de publica??o ou o contexto do workflow.
## S08 e visual: saldo conservado
**A melhoria da janela S08 n?o est? conclu?da.** Os documentos recuperados referem a pend?ncia sem especifica??o execut?vel. A etapa de licenciamento tamb?m chamada S08 n?o ? usada como substituto. Foi solicitada somente a refer?ncia da conversa/ficheiro j? fornecido para recuperar o requisito, n?o uma nova defini??o. Nenhum dos commits ou testes acima encerra S08.
**A revis?o visual n?o est? conclu?da.** Causa correcta da interrup??o: a ferramenta n?o conseguiu confirmar com confian?a o URL actual no Windows; n?o foi a tecla Escape e n?o constitui falha demonstrada do portal. Retomar quando o acesso estiver dispon?vel ou pela equipa. Conferir desktop/mobile, foco/teclado, header/contador e aliases com contexto, apoio/rodap?, ficha/data, pr?-dossier/impress?o e curadoria (pr?via, confirma??o, recupera??o, pagina??o e altera??es por gravar). Preservar provas aceites anteriores; registar ambiente, commit e resultado de cada percurso.
O cluster 55441 permanece ligado. Existem servidor de desenvolvimento e workers do portal em execu??o; aus?ncia moment?nea de liga??es n?o prova aus?ncia de depend?ncias. N?o foi encerrado nem reiniciado qualquer servi?o. Instala??o continua pendente e separada.
## Recibos locais preservados
- `outputs/directive-implementation-20261110/curation-qa.json` ? SHA256 `114e90597c03e17c318c4f52d6af83dac60bb74599db6f37b12c4bd735f4531a`.
- `outputs/directive-implementation-20261110/native-curation-qa.json` ? SHA256 `65cf7aff819d00fb3b23e5567be06caf3cb7683049126ae2eacd17c5df9babaa`.
- `outputs/directive-implementation-20261110/curation-history-qa.json` ? SHA256 `df25fde75f17ac3eb46eb65266e8ec62f1048432803cb8e4aa404d2787cdd7e7`.
- `outputs/directive-implementation-20261110/workspace-qa.json` ? SHA256 `ef20640b25689e0c54e9906efe54846d2401487b633b57bf42f650e2ecafe520`.
- `outputs/directive-implementation-20261110/build-final.log` ? SHA256 `167daca7b4284a7fe652a472a631a551fce5455d2364a9f6a783320bc962d798`.
## Recuperacao das fontes Portal V4
A Tempo indicou a pasta `Portal V4` em resposta ao pedido de referencia. Inventariados dez DOCX e `cartografia_moz.xlsx`; originais preservados. Texto dos dez DOCX conferido, sem referencia literal a S08, S-08 ou S8. A imagem incorporada, identica em seis documentos, e um diagrama de entidades CRM, sem numeracao de janelas. Isto nao prova que o requisito nao foi transmitido; falta ligar a designacao S08 ao titulo ou URL da interface. Foi solicitada apenas essa identificacao. Nenhuma alteracao funcional foi atribuida por inferencia a S08.
`correccoes a introduzir no portal.docx` descreve ajustes de formulario, perfil/contacto, pesquisa contextual, localizacao, estrutura financeira, emprego, operacao fiscal, necessidades, documentos e revisao, alem de catalogo, procedimentos e parcerias. Estes ambitos nao identificam inequivocamente uma janela S08 e nao autorizam declarar todo esse conjunto como o seu saldo. Documentos de referencia nao substituem as decisoes expressas da Tempo ou as provas aceites.
Hashes dos originais inventariados:
- `Matriz detalhada de oportunidades por sector.docx`: `73d7f7792631d144d69616061354b88fcbbedce825d0af533d5317ed34c8ccd6`.
- `Portal de investimentos v4.docx`: `a0438b98c56ce930453f3eb519a952b3274c01fc421b2a2b4c5a994e988a84bb`.
- `upgrade do portal de investimentos v6 programacao.docx`: `e1169a7743b13ff6c950d3ffd4672a2afea32e81da84885e11154b7d8de9241e`.
- `upgrade do portal de investimentos -casos .docx`: `b44ec86d313eee6790be68a829f43e00c21e191c16cb89b4f8c37dbddc26d0ba`.
- `correcções a introduzir no portal.docx`: `bfb24adfdc43afac8492b30b6bb2b7c57d5a3d57c7cd36008d9fdd5a80cd7dc5`.
- `cartografia_moz.xlsx`: `2c5728c46973866371139cb080b1b1837bb980a43ae91693f0b58f2263a6c455`.
- `simulador fiscal.docx`: `55997917b3bd85727cffac880b6c46fc85a78fef6a005798b9a110a0bca60fd0`.
- `upgrade do portal de investimentos v1.docx`: `f70ce3075523c6278e00dce15b9c7acf2b690679e16959ae5f7d007e754cfc6e`.
- `upgrade do portal de investimentos v2.docx`: `51b8f0ad3296d1064a5c12b450874fc72640869f10a164319cd7f3153dd3a086`.
- `upgrade do portal de investimentos v5 programacao.docx`: `edece80c0519537244f8a8b86456af0aa92de1c526f791bb0ee9b85af7a7e8b2`.
- `simulador fiscal v1.docx`: `f038d338fa0247ddd70f9dd34e72b080f9b7c93acfc02aec0d433bca1ee606d2`.
### Esclarecimento da Tempo sobre S08
Em resposta ao pedido do titulo/URL da janela, a Tempo informou que nao dispoe dessa referencia adicional. Encerra-se esse pedido de esclarecimento, sem exigir novamente a mesma informacao. S08 permanece no saldo como pendencia de ambito por identificar; nao ha criterio executavel suficiente para lhe atribuir alteracoes de codigo ou provas de conclusao. Nao e declarada resolvida, cancelada ou equivalente a etapa de licenciamento homonima. Os commits preparados e as provas aceites mantem-se; revisao visual e instalacao continuam pendentes e separadas. Cluster QA 55441 mantido ligado.
## S08 ? ?mbito esclarecido e incremento implementado
A Tempo esclareceu expressamente que S08 ? a melhoria do percurso existente de **licenciamento pr?vio**. Esta decis?o substitui as anota??es anteriores de ?mbito por identificar; n?o ? uma funcionalidade nova. Commit de c?digo e provas: `6734441855059d01bd087b323ee87ce03631d9ba`, separado do pacote contador/aliases e dos restantes ajustes.
S08 mostra o estado actual uma vez no t?tulo; o selector de estado mant?m a fun??o de edi??o. A etapa passa a sec??o de largura completa, com formul?rio e refer?ncia vis?veis, retirando tr?s blocos expans?veis internos. A fase conserva a organiza??o do percurso. O bot?o ?Guardar registo? recebe destaque e mant?m o bloqueio durante a grava??o. Entrada inicial e mudan?a de fragmento `#S08` abrem os ancestrais da etapa, deslocam e d?o foco ? etapa existente. N?o h? nova navega??o, altera??o da query, remount dos campos, selec??o de outra oportunidade ou confirma??o autom?tica. Alvos desconhecidos, ausentes ou exteriores ao percurso n?o produzem efeito.
Contrato de grava??o preservado: mesmo endpoint e transforma??o, vers?o actual do dossier, oportunidade, revis?o e confer?ncia de contexto, estado/ac??o, fundamento, refer?ncia, documento, entidade e data; mesmos campos condicionais de revis?o, erros e `preserveScroll`. Guardas `preparation_allowed`, `canWrite` e `canReview` conservadas. Nenhum controlador, servi?o de neg?cio, pol?tica, schema, hist?rico ou dado foi alterado neste incremento. As restantes etapas conservam os seus blocos expans?veis; a abertura por fragmento usa os alvos existentes.
**Verifica??o dirigida:** sete testes frontend aprovados: estado/ac??o vis?veis, grava??o/contexto/vers?o/campos, leitura/revis?o e processamento/erros, preserva??o das outras etapas, abertura/foco, alvos inv?lidos e efeito inicial/hashchange/remo??o do listener. TypeScript e lint dirigido aprovados; build QA privado aprovado. Os testes usam harness de componentes e elementos simulados: n?o constituem revis?o visual, navegador real nem nova prova HTTP das autoriza??es do servidor. N?o foi usada qualquer base ou repetida a suite aceite do backend. Recibo `outputs/s08-licensing-20261110/verification.json` e log `outputs/s08-licensing-build-20261110.log`.
Matriz existente: anotada apenas AUD-LEG-07 nas colunas de fonte/evid?ncia, com 3555 linhas e todas as c?lulas anteriores preservadas. Nenhum requisito amplo promovido a conclu?do. **Implementa??o e provas dirigidas deste recorte conclu?das; revis?o visual e instala??o continuam pendentes e separadas.** Conferir pela equipa ou quando o acesso permitir: entrada directa e hashchange, desktop/m?vel, foco/teclado, campos por gravar e grava??o/erro com os perfis existentes. A ferramenta n?o conseguiu confirmar o URL; isso n?o demonstra falha do portal. Sem push, instala??o ou encerramento do cluster QA 55441.
@@ -1,3 +1,416 @@
## 11/11/2026 — Energia incorporada pelo mesmo percurso de Mineração, em QA
**Implementação:** o sector existente `/sectores/energia-especifica` passa a apresentar o documento fornecido com7segmentos,11áreas de cadeia de valor,10projectos de referência,4riscos e24fontes com as ligações originais. Gás/upstream/construção/midstream/industrialização/mercado final,redes/hídrica,solar/baterias/eólica,hidrogénio,biocombustíveis e parques verdes mantêm o seu contexto. As três centrais solares aparecem uma vez cada,com capacidade,promotores,financiamento e estado referidos. Metoro conserva56milhõesUSD e47,3milhõesEUR,atribuídos às fontes,sem conversão ou escolha silenciosa. Previsão,operação,desenvolvimento,estudo e proposta não são tratados como estados equivalentes.
**Localização e continuidade:** filtros usam apenas bacias,corredores,localidades e províncias explicitamente referidos. Buzi não recebe província por aproximação;Maputo permanece por precisar quando não discriminado;hidrogénio/biocombustíveis sem implantação mantêm local por definir. Escolher área → seleccionar dossier existente ou criar → guardar → recuperar → usar como evidência na preparação/SWOT. Energia e Mineração reutilizam o mesmo serviço/componente de referências do sector e o mesmo escritor/histórico do dossier,sem módulos,tabelas,roles ou permissões novos. Podem coexistir no mesmo dossier sem substituir escolhas deliberadas. Origem,linha/parágrafo,versão,actor e data preservados. Referências não criam ofertas,concursos abertos,direitos,licenças,promotores ou conclusões SWOT.
**QA:** sete casos nativos dos dois sectores/166asserções no cluster55441,em transacções revertidas;cinco casos de interface com filtros,retoma,continuidade e locais por definir;nove casos já dirigidos aos componentes afectados,sem repetir operações aceites. Análise PHP,tipos,lint e compilação registados no fecho em `outputs/sector-resource-20261111/verification.json`. HTMLs dos componentes reais emSSR são evidência de renderização,não revisão visual de navegador. Documento deEnergia intacto,SHA256 `2d9d94ac70115ade43c4093a4d5b302aaa93623a149f936615c38ba67b1e2bd2`;extracção reproduzível em `tools/prepare-energy-reference.py`. As referências legais permanecem documentais;nenhum cálculo,benefício ou regra é activado pela incorporação.
**Saldo:** Mineração e Energia implementadas emQA,agrupadas para entrega Git com backup/restauro/reversão;instalação e conferência visual pendentes. Referência da homologação6d3bf293 e270instituições preservadas. Não há publicação de projectos reais nem envio de dados a terceiros. A fonte precisa de dados específicos para ofertas concretas e localização mais exacta;isso não impede consultar/preparar estes percursos. Restantes módulos e resíduos fiscaisL1–L7,L8,L9–L16 mantêm o seu saldo anterior.
## 11/11/2026 — Mineração e cadeia de valor implementadas em QA, sem instalação
O documento «Minérios de Moçambique e a sua localização» foi integrado na página existente `/sectores/mineracao`, sem módulo paralelo: **11 grupos minerais,10 áreas de cadeia de valor,6 projectos de referência,12 indicadores e13 fontes**. Consulta por localização,grupo mineral ou actividade; cada área mostra o âmbito territorial,possibilidades de fornecimento,logística,energia,beneficiamento ou transformação. Produção,capacidade,recursos e previsões conservam período e atribuição. São referências para preparação,não uma oferta de concessão ou projecto disponível.
**Percurso demonstrado:** sector → Cabo Delgado/Grafite → cadeia de valor → escolher dossier existente ou criar → confirmar → recuperar no mesmo dossier → referência disponível como evidência SWOT. A escolha conserva grupo mineral,área,localização,texto territorial,versão/hash da fonte,actor e data. Áreas distintas são acrescentadas sem duplicar o sector,alterar o título ou substituir escolhas anteriores. Repetição recupera a mesma referência; chave de criação reutilizada com outra selecção recusa409. Dossier alheio403,encerrado409,fonte/local/grupo incompatíveis409/422. Sem coordenadas,direitos mineiros,promotores ou relações geográficas inferidos. “Maputo” mantém província/cidade por precisar quando o original não distingue. A localização agregada de um grupo não prova a ocorrência de todos os minérios em todos os locais.
**QA dirigido:** quatro casos nativos PostgreSQL55441/82asserções,em transacções revertidas; três casos de interface para filtros,retoma e continuidade nas duas escolhas de dossier; nove casos dos componentes afectados reutilizados sem repetir operações aceites. Análise PHP,tipos,lint e compilação no fecho registados em `outputs/mining-reference-20261111/verification.json`. Não é conferência visual de navegador. Original intacto,SHA256 `7202fb5986b40e62347867c653770030683ffec2701a2921e84342f15e9f3d8a`; extracção reproduzível em `tools/prepare-mining-reference.py`,linhas de origem conservadas. Instruções contidas no documento não foram convertidas em regras de produto.
**Estado:** implementado em QA; instalação deste conteúdo ainda pendente de autorização específica da entrega. Homologação de referência `6d3bf293`,270instituições publicadas e entregas aceites preservadas. Não se criam/publicam dez oportunidades concretas nem se importam anexos para a biblioteca. Faltam instalação e conferência visual deste recorte; projectos concretos,localizações mais precisas e condições dependem dos respectivos dados. O restante saldo integral por módulo e fiscalL1–L7,L8 eL9–L16 permanece aberto nos seus recortes.
## 11/11/2026 — perfis uniformes e menu de seis grupos instalados em6d3bf293
Instalação expressamente autorizada deeca67191 concluída na revisão integrada `6d3bf2937ee6f5a58267967516560ba73d2cc936`,conferida às12:38:49Africa/Johannesburg(10:38:49UTC). A guarda inicial detectoua400802b já instalada e parou antes de escrever. Comparação delimitada identificou seis alterações independentes de tradução/cabeçalho fixo,integradas e preservadas;os quinze ficheiros aprovados deeca67191 permaneceram idênticos. Pacote reconciliado aplica/reverte exactamente sobrea400802b;pacote original preservado. Não foi necessária nova decisão de âmbito.
Utilizável em `/fundos` e nos detalhes deQuorus/Moza:mesmo componente público,descrição e produtos uma vez,categoria/tipo numa linha,contactos e moradas conhecidos sem duplicação;sem secções vazias,avisos genéricos,preços antigos ou notas internas. Menu principal **Início–Descoberta–Realização–Suporte–Sobre nós–Mais**,com concursos emDescoberta e acessos de trabalho preservados emMais. Fontes,históricos,contactos e moradas originais continuam no editor;nenhuma deduplicação de dados foi executada.
Backup novo/restauro isolado conferidos:190tabelas/369ficheiros. Vinte e um ficheiros por hash(15aprovados+6preservados);revisão,entrada,up,login,Concursos,fundos e simuladorHTTP200. Configuração intacta,zero migrações/importações/republicações. Totais/checksums idênticos ao backup nas sete tabelas protegidas:funding_programmes,funding_programme_versions,funding_programme_interests,desk_cases,simulation_form_drafts,labour_demands,training_capacities.275registos/270instituições publicadas preservados. Recibo `outputs/funding-uniform-menu-20261111/installation.json` e reconciliação no mesmo directório. Pacote `outputs/entrega-financeiras-menu-reconciliada-20261111.zip`,SHA256 `dc0be0d20569a96a3338ba140b8430013f146deb6eafd73b66343c2841d91e16`;reversão paraa400802b,preservando trabalho alheio.
Este conjunto deixa de ser pendência de implementação/instalação;conferência visual permanece separada. Não requer seleccionar/publicar novamente instituições. Restante saldo de módulos e fiscal conservado;portal não declarado integralmente concluído.
## 11/11/2026 — uniformização Quorus/Moza e menu de seis grupos em QA
Correcção orientada pelos exemplos expressos do proprietário:lista e detalhe reutilizam agora **o mesmo componente público**. Descrição única,categoria/tipo numa linha,produtos/serviços uma vez,contactos e moradas apenas quando disponíveis. Quorus:descrição de corretagem eSeguros uma vez;sem secções vazias ou avisos genéricos. Moza:descrição paraPME,CréditoPME/instrumentos uma vez,umtelefone e umamorada;sem notas de publicação,preços antigos,comissões/garantias por reconfirmar,entidade responsável repetida,sectores/cobertura desconhecidos ou referências institucionais duplicadas. Não inventa dados nem altera condições guardadas. Ligações dos produtos/contactos permanecem acessíveis uma vez;moradas diferentes são preservadas.
Menu principal exactamente **Início–Descoberta–Realização–Suporte–Sobre nós–Mais**. Concursos movido paraDescoberta;Biblioteca eInstituições financeiras com os nomes pedidos;conteúdos deSobre nós/Mais preservados. Acesso de trabalho deslocado paraMais,sem perder permissões;conta/contexto/licenciamentoS08 e filtros preservados. Instruções permanentes actualizadas com o padrão pedido.
**QA:**33verificações dirigidas aprovadas,com três casos novos:exemplos públicos exactos nas duas páginas,deduplicação conservadora e cabeçalho real com seis entradas em quatro combinações(público/autenticado,móvel/computador). Reutilizadas provas dos fluxos de interesse/dossier/editorial e das permissões,não reexecutadas na base. Fontes de270instituições intactas,sem migração/importação/republicação. Artefactos `outputs/funding-uniform-menu-20261111/verification.json` eHTMLs reais emSSR. Não é conferência visual de navegador;tipos/lint/compilação aprovados;pacote Git com aplicação/reversão conferidas no fecho.
**Estado:** implementado em QA,não instalado;homologação mantém `a59142ca` e270instituições publicadas. Restam instalação e conferência visual;não requer seleccionar/publicar novamente instituições. Restante saldo de módulos e fiscal preservado. Próximo trabalho:entregar este conjunto compatível e continuar os resíduos de implementação acordados.
## 11/11/2026 — apresentação financeira profissional instalada ema59142ca
Instalação expressamente autorizada,executada pelo processo Git existente e conferida às11:38:54Africa/Johannesburg(09:38:54UTC): `a59142caa2fc277975be82fb550223ff7d949594`. O directório `/fundos` e o detalhe apresentam descrição e produtos/serviços conhecidos,sem usar texto de importação,fichas ou documento gerador como apresentação. Descrições específicas são preservadas;resumo genérico exacto passa a uma descrição pela natureza publicada. Não são inventadas ofertas para instituições sem produtos específicos identificados. Metadados/fontes/histórico mantidos no editor;ligações institucionais e datas úteis acessíveis separadamente. Orientação transversal registada emAGENTS.
Backup novo/restauro isolado conferidos:190tabelas/369ficheiros. Dezasseis fontes por hash;revisão,entrada,up,login,Concursos,fundos e simuladorHTTP200. Configuração intacta,zero migrações/importações/republicações do lote. Totais/checksums idênticos ao backup nas sete tabelas protegidas:funding_programmes,funding_programme_versions,funding_programme_interests,desk_cases,simulation_form_drafts,labour_demands,training_capacities.275registos/270instituições publicadas preservados. Recibo `outputs/funding-public-editorial-20261111/installation.json`;pacote `outputs/entrega-financeiras-editorial-20261111.zip`,SHA256 `531db038a46ee32606ebbed0aab43630e2f68b05569ed3a6977b85b59ea68b8b`. Revisão8f295f05 preservada para reversão.
Este delta deixa de ser pendência de implementação/instalação. Visual permanece separado,enriquecimento de produtos específicos depende de informação disponível. Não é necessário seleccionar/publicar novamente as270instituições. Restantes módulos e resíduos fiscais conservam o saldo existente;portal não declarado integralmente concluído.
## 11/11/2026 — apresentação pública profissional do directório financeiro em QA
**Pedido do proprietário:** descrições profissionais e apelativas,com descrição/produtos/serviços úteis ao investidor,sem texto de fichas,importação ou documento gerador;orientação transversal registada emAGENTS. O directório e o detalhe passam a uma apresentação editorial comum:reconhece exclusivamente o resumo genérico exacto da importação e usa a natureza publicada da entidade;descrições editoriais específicas são preservadas e limpas da fórmula genérica final. Produtos identificados são apresentados pelos nomes/instrumentos existentes;os sete catálogos específicos disponíveis não são extrapolados para outras entidades. Nas restantes,apresentam-se áreas de serviço conhecidas ou contacto para conhecer soluções,sem inventar produtos/condições.
Retirados da apresentação pública nomes de ficheiros,hashes,linhas de importação,códigos de sucessora e descrições de registo interno. Ligações institucionais e datas úteis continuam acessíveis;metadados e fontes permanecem intactos no cadastro/editor. Instituições históricas/fundidas continuam identificadas como tal. Instituições sem chamada não mostram uma secção vazia de candidatura. Textos da selecção guardada e chamadas públicas dePorque investir/Serviços ajustados à linguagem do investidor. Fluxos de interesse,dossier,edição,revisão/publicação e filtros preservados.
**QA:** nove verificações afectadas aprovadas,três novas incluindo270referências fornecidas,descrições específicas preservadas,produtos não inventados e renderização dos componentes públicos reais de lista/detalhe. Sem escrita em dados activos ou nova publicação do lote;é uma projecção editorial dos dados já publicados. Evidência `outputs/funding-public-editorial-20261111/verification.json` e quatroHTMLs;isto não constitui conferência visual no navegador. Tipos/lint/compilação aprovados;pacote Git com aplicação/reversão conferidas no fecho.
**Estado:** implementado em QA,não instalado;homologação mantém `8f295f05` e270instituições publicadas. Restam instalação/visual e enriquecimento de produtos específicos quando houver informação disponível. O saldo dos outros módulos e resíduos fiscais permanece inalterado. Não é necessário seleccionar ou publicar novamente as270instituições para esta alteração de apresentação.
## 11/11/2026 — rectificação do volume PEC instalada em8f295f05
Instalação expressamente autorizada,executada pelo processo Git existente e conferida às11:03:24Africa/Johannesburg(09:03:24UTC): `8f295f05510c786afeb8ea1dfa692e08d90a0d30`. No simulador fiscal,L8,**Rectificar volume na simulação PEC** conserva o valor original e o corrigido,com escolha explícita/fonte e efeito na estimativa apresentados no relatório. A hipótese de tesouraria anterior exige reconciliação se o total mudar;nenhum comprovativo pago,dedução ou saldo oficial é duplicado. Não executa pagamentos nem submete declaração rectificativa.
Backup novo/restauro isolado conferidos:190tabelas/369ficheiros. Quinze fontes por hash;revisão,entrada,up,login,Concursos,fundos e simuladorHTTP200. Configuração intacta,zero migrações/importações. Totais/checksums idênticos ao backup nas sete tabelas protegidas:funding_programmes,funding_programme_versions,funding_programme_interests,desk_cases,simulation_form_drafts,labour_demands,training_capacities.275fichas/270instituições publicadas preservadas. Recibo `outputs/irpc-pec-correction-20261111/installation.json`;pacote `outputs/entrega-pec-rectificacao-20261111.zip`,SHA256 `559e4477059b092a193d990f7dfb6cb50007d43c24ec00e2484326f076e0c973`. Revisãoa708f079 preservada para reversão.
Este recorte29(3) deixa de ser pendência de implementação/instalação. Visual permanece separado;correcções fora do recorte têm âmbito próprio. Restam última base29(5),PPC excedente,períodos/regimes especiais e demais resíduos L1–L7/restanteL8/L9–L16 e módulos. Próximo trabalho:primeiro residual de simulação executável com as fontes aceites,sem reabrir este recorte. Portal não declarado integralmente concluído.
## 11/11/2026 — rectificação do volume PEC na simulação em QA
**Recorte29(3) avançado:** fonte documental permite rectificar o volume de negócios2025 usado na estimativa PEC2026. Valor original,valor corrigido,titular,exercício e fundamento ficam conservados;escolha explícita entre aplicar a correcção e manter o original. Não submete declaração rectificativa. A estimativa recalcula0,5%,limites e PPC anteriores uma única vez;relatório mostra origem,valor corrigido e efeito na estimativa. Mudança da base/titular/exercício exige reconferência;fonte ou valor ausente não é zero.
Cálculo→API privada→recuperação→HTML/relatório do mesmo dossier em cinco cenários:aumento40.000→90.000MZN,redução40.000→20.000,original conservado40.000,base divergente e correcção para volumezero indeterminados. Hipótese de caixa anterior40.000 deixa de reconciliar quando o total muda;não é sobrescrita. Saldo anual138MZN,comprovativos pagos e histórico anterior preservados. QA55441 existente:uma prova nativa/47asserções com rollback,cinco testes novos de cálculo/interface e40afectados aprovados;tipos/lint/compilação aprovados. Provas `outputs/irpc-pec-correction-20261111/*-verification.json`;pacote Git com aplicação/reversão conferidas no fecho. Não é conferência visual.
**Estado:** implementado em QA,ainda não instalado;homologação mantém `a708f079` e270instituições publicadas. Fonte:RIRPC,Decreto9/2008,art29(3),BR16/I16-04-2008,original já aceite SHA256 `fc373ff5637edf75ec6a9595dec8763a8503d4ee786e3b7d8f1c742eae9d80ee`. Sem migração,base,tabela ou direito novo. Restam instalação/visual,última base29(5),PPC excedente,períodos/regimes especiais e demais resíduos fiscais L1–L7/restanteL8/L9–L16. Correcções fora deste recorte conservam âmbito próprio;L8 e portal não são declarados integralmente concluídos. Próximo trabalho:primeiro residual de simulação executável pelas fontes aceites,sem reabrir este recorte.
## 11/11/2026 — finalidade de viabilidade e projecção PEC instaladas ema708f079
Instalação expressamente autorizada,executada pelo processo Git existente e conferida às10:45:50Africa/Johannesburg(08:45:50UTC): `a708f079b318a13100644c27287f1caa854ecf1d`. No simulador fiscal,L8,os ecrãs apresentam **Simular PPC/PEC**,estimativas fiscais e hipóteses de tesouraria para avaliar a viabilidade do negócio. A hipótese PEC permite distribuir valores por três meses ou concentrar a estimativa num mês escolhido,com soma conferida e prazo legal não certificado. Rascunho,recuperação,histórico e relatórios preservam escolhas;dados documentais já pagos permanecem distintos. O portal não executa pagamentos,nem submete declarações fiscais.
Backup novo/restauro isolado conferidos:190tabelas/369ficheiros. Vinte e quatro fontes por hash;revisão,entrada,up,login,Concursos,fundos e simuladorHTTP200. Configuração intacta,zero migrações/importações. Totais/checksums idênticos ao backup nas sete tabelas protegidas:funding_programmes,funding_programme_versions,funding_programme_interests,desk_cases,simulation_form_drafts,labour_demands,training_capacities.275fichas/270instituições publicadas preservadas. Recibo `outputs/irpc-pec-plan-20261111/installation.json`;pacote `outputs/entrega-simulacao-viabilidade-20261111.zip`,SHA256 `a4dbbe26ca72230e3ff7a5c2a744c187a93380a4857e522b1db5ee17acce3ca5`. Revisãobcbbe51c preservada para reversão.
Este delta deixa de ser pendência de implementação/instalação. Conferência visual permanece separada. Execução de pagamentos não é critério de conclusão deste simulador,conforme clarificação expressa registada emAGENTS. Próximo trabalho:primeiro residual de simulação executável pelas fontes aceites. Base29(5),rectificação29(3),PPC excedente,períodos/regimes e restantes resíduos L1–L7/restanteL8/L9–L16 e módulos mantêm o saldo;portal não declarado integralmente concluído.
## 11/11/2026 — finalidade do simulador e projecção de tesouraria PEC
**Clarificação expressa do proprietário:** o portal simula impostos para avaliar a viabilidade do negócio; não faz pagamentos. A apresentação anterior de «obrigação» e «plano de pagamento» criava uma expectativa errada de execução. Ecrãs PPC/PEC passam a **Simular PPC/PEC**,estimativa fiscal e projecção de tesouraria. Fontes legais e dados documentais já pagos continuam a informar o cálculo; não são operações executadas pelo portal. A execução de pagamentos não é pendência deste simulador.
**Implementado em QA:** hipótese PEC distribuída por Junho/Agosto/Outubro com montantes explícitos cuja soma corresponde exactamente ao total,ou hipótese concentrada num mês escolhido. Não inventa igualdade/arredondamento ou validação de prazo para a opção única. Valores desconhecidos,divergência da soma,total alterado e ausência de estimativa positiva invalidam a projecção dependente,sem bloquear o cálculo fiscal. Trocar hipótese conserva edições;resumo com Alterar. Rascunho privado,recuperação,histórico e relatório do mesmo dossier reutilizados. As estimativas não alimentam os comprovativos pagos nem alteram deduções anuais.
Cinco verificações novas de cálculo/interface e35afectadas aprovadas;percurso sintéticoAPI/recuperação/relatório em quatro cenários(distribuído,concentrado,divergente,total alterado),com saldo anual138MZN conservado. Provas `outputs/irpc-pec-plan-20261111/*-verification.json`. Sem migração,base,papel ou permissão nova. Tipos/lint/compilação aprovados;uma prova nativa/38asserções com rollback. Pacote Git e reversão conferidos no fecho. Isto não é conferência visual.
**Estado:** QA,ainda sem instalação;homologação permanece `bcbbe51c` e270instituições publicadas. Restam instalação/visual e recortes fiscais de base29(5),rectificação29(3),PPC excedente,períodos/regimes e outras regras de simulação;prazo legal da hipótese única não é certificado. L1–L7/restanteL8/L9–L16 e demais módulos permanecem no saldo,sem transformar o portal em sistema de execução fiscal. Próximo trabalho:primeiro residual de simulação executável com fontes aceites.
## 11/11/2026 — total e calendário PEC instalados embcbbe51c
Instalação expressamente autorizada,executada pelo processo Git existente e conferida às10:28:18Africa/Johannesburg(08:28:18UTC): `bcbbe51c2fc279d5b2d85412f7bcb6ca2423a9b6`. Utilizável no simulador fiscal,fecho anual L8: **Preparar obrigação PEC**,com total ordinário2026 baseado no volume documental2025,limites antes dos PPC pagos anteriores,mesesJunho/Agosto/Outubro e dispensa no ano de início efectivo. Guardar/recuperar pelo rascunho privado e apresentar no relatório do mesmo dossier. Não cria pagamento,dedução anual ou reembolso.
Backup novo/restauro isolado conferidos:190tabelas/369ficheiros. Catorze fontes por hash;revisão,entrada,up,login,Concursos,fundos e simuladorHTTP200. Configuração preservada,zero migrações/importações. Sete tabelas protegidas mantêm totais/checksums do backup:funding_programmes,funding_programme_versions,funding_programme_interests,desk_cases,simulation_form_drafts,labour_demands,training_capacities.275fichas/270instituições publicadas preservadas. Recibo `outputs/irpc-pec-obligation-20261111/installation.json`;pacote preservado `outputs/entrega-pec-obrigacao-revisada-20261111.zip`,SHA256 `a9d9bb089305003487695969d653c7f876a913fce44bbbffb5191c1a792a9184`. Revisãofbff2660 preservada para reversão.
Este recorte deixa de ser pendência de implementação/instalação;visual permanece separado. Restam montantes por prestação/pagamento único,última base29(5),rectificação29(3),PPC excedente,períodos/regimes especiais,guias/juros. Próximo trabalho:primeiro desses casos executável pelas fontes aceites,sem repetir o recorte instalado. L1–L7/restanteL8/L9–L16 e restantes módulos continuam no saldo;portal não declarado integralmente concluído.
## 11/11/2026 — total e calendário PEC ordinário em QA
**Novo recorte L8:** preparação do total PEC2026 pelo volume de negócios documental2025:0,5%,limites30.000/100.000MZN aplicados antes de deduzir PPC efectivamente pagos no ano anterior. MesesJunho/Agosto/Outubro,sem inventar dia limite ou importar o arredondamento PPC. Dispensa do ano de início efectivo da actividade29(4) fundamentada separadamente. Fonte:RIRPC,Decreto9/2008,art29(1–5),BR16/I16-04-2008,original SHA256 `fc373ff5637edf75ec6a9595dec8763a8503d4ee786e3b7d8f1c742eae9d80ee`, já preservado no acervo.
Formulário progressivo reutiliza titular/exercício e conserva valores ao ocultar campos sem aplicação; rascunho privado e relatório do mesmo dossier reutilizados. Total preparado não é PEC pago,nem cria dedução anual,reembolso,guia ou consumo de saldo oficial. Ausência de informação não equivale a zero. Sem volume no ano anterior exige base29(5);PPC superiores ao montante limitado ficam por reconciliar,sem obrigação negativa. Esses casos não impedem o cálculo anual independente.
**Percurso sintético:** cálculo→API privadaPOST/PUT→GET→recálculo→HTML/relatório:40.000MZN ordinário,20.000mínimo antes de PPC,90.000máximo antes de PPC,dispensa inicial0,volumezero e desconhecido indeterminados. Histórico e saldo anual138MZN preservados. QA55441 existente,uma prova nativa/56asserções com rollback,sem nova base/tabela/porta/papel/migração. Seis verificações novas de cálculo/interface e29afectadas aprovadas; tipos/lint/compilação aprovados. Provas `outputs/irpc-pec-obligation-20261111/*-verification.json`;pacote Git com aplicação/reversão conferidas no fecho.
**Saldo:** implementado em QA,ainda não instalado. Homologação mantém `fbff2660` e270instituições publicadas. Restam entrega Git e visual;montantes por prestação/opção de pagamento único,última base29(5),rectificação29(3),PPC acima do limite,períodos/regimes especiais,guias/juros permanecem abertos. L8 não é integralmente concluído;resíduos L1–L7/restanteL8/L9–L16 e restantes módulos conservados. Próximo trabalho:primeiro desses casos executável com as fontes aceites,sem reabrir os recortes entregues.
## 11/11/2026 — obrigação PPC instalada emfbff2660
Instalação expressamente autorizada e executada pelo processo Git existente: `fbff2660f280f53fe079221164bb21917032ac70`, conferida às09:04:59Africa/Johannesburg(07:04:59UTC). Utilizável no simulador fiscal,fecho anual L8: **Preparar obrigação PPC**,com imposto anterior documental líquido de retenções,80%,três prestações arredondadas individualmente por excesso ao milhar e calendárioMaio/Julho/Setembro. Guarda e recuperação pelo rascunho privado existente e inclusão no relatório do dossier. Obrigação preparada não equivale a pagamento,nem cria dedução de PPC.
Backup novo/restauro isolado conferidos:190tabelas/369ficheiros. Catorze fontes do delta por hash e revisão,entrada,up,login,Concursos,fundos e simuladorHTTP200. Configuração preservada,zero migrações/importações;sete tabelas protegidas com totais/checksums idênticos ao backup:funding_programmes,funding_programme_versions,funding_programme_interests,desk_cases,simulation_form_drafts,labour_demands,training_capacities.275fichas/270instituições publicadas preservadas. Recibo `outputs/irpc-ppc-obligation-20261111/installation.json`; pacote `outputs/entrega-ppc-obrigacao-20261111.zip`,SHA256 `ab72a11b9306e4503ab92669c47ec592b2510aa01189ebab1d5216bb40d3769a`. Revisão6805b606 preservada para reversão.
Este recorte deixa de ser pendência de implementação/instalação. Conferência visual permanece separada. Próximo residual fiscal:PEC/obrigações e outros casos executáveis com fontes aceites;anualização/períodos especiais,dispensas/guias/juros e resíduos L1–L7/restanteL8/L9–L16 mantêm-se explícitos. Restantes módulos permanecem no saldo existente;portal não declarado integralmente concluído.
## 11/11/2026 — obrigação PPC ordinária de L8 em QA
**Pendência avançada:** o fecho anual existente passa a preparar a obrigação PPC2026 com liquidação documental do exercício civil completo2025: imposto anterior21(1) líquido de retenções,80%,três prestações iguais arredondadas individualmente por excesso ao milhar de meticais. Calendário por mês:Maio,Julho,Setembro,sem inventar dia limite. Fonte:RIRPC,Decreto9/2008,arts27(1)(a)/28(1–3),BR16/I16-04-2008,original SHA256 `fc373ff5637edf75ec6a9595dec8763a8503d4ee786e3b7d8f1c742eae9d80ee`, já preservado no acervo. Não usa colecta projectada corrente como liquidação anterior.
Reutiliza titular/exercício e rascunho privado existentes; formulário progressivo conserva edições deliberadas ao trocar enquadramento. Obrigação preparada e comprovativos efectivamente pagos permanecem distintos: não cria pagamento,dedução,saldo oficial,guia ou juros. Campos vazios,fonte ausente,divergência de titular/período,base negativa e período especial ficam indeterminados,sem impedir o fecho anual independente. Zero explícito permanece zero. Anualização28(3),dispensas,PEC e restantes regimes ficam separados.
**Prova sintética:** cálculo→POST/PUT→GET privado→recálculo→HTML/relatório do mesmo dossier em cinco cenários:72.000MZN ordinário,75.000após arredondamento,zero,valor desconhecido e período especial indeterminados. Saldo anual138MZN e comprovativos inalterados; histórico anterior conservado. Uma prova nativa/47asserções com rollback no QA55441 existente; cinco verificações novas de cálculo/interface e24afectadas aprovadas. Tipos/lint/compilação aprovados; pacote Git com aplicação/reversão conferidas no fecho. Provas `outputs/irpc-ppc-obligation-20261111/*-verification.json`.
**Estado:** implementado em QA, ainda sem instalação. Homologação mantém `6805b606`,Educação entregue e270instituições publicadas. Restam entrega Git deste delta e visual; L8 não está integralmente concluído. Preservam-se os resíduos L1–L7,restanteL8/L9–L16 e restantes módulos. Próximo trabalho fiscal: PEC/obrigações e demais casos executáveis com as fontes aceites,sem reabrir PPC ordinário.
## 11/11/2026 — retoma da revisão de Educação instalada em6805b606
Instalação autorizada pelo utilizador, executada pelo processo Git existente e conferida às08:46:05Africa/Johannesburg(06:46:05UTC): `6805b606a106fbed410b191ed778c0fad7c8e913`. Guardar e recuperar a preparação privada da revisão interna de procura/capacidade fica utilizável na homologação, pelo detalhe existente em `/management/education`. A gravação privada não decide nem aprova; preserva notas, versão analisada e a chave de uma intenção incerta.
Backup novo e restauro isolado conferidos:190tabelas/369ficheiros. Catorze fontes conferidas por hash; revisão,entrada,up,login,Concursos,fundos e simuladorHTTP200. Configuração intacta, zero migrações/importações. Totais e checksums inalterados nas sete tabelas protegidas:funding_programmes,funding_programme_versions,funding_programme_interests,desk_cases,simulation_form_drafts,labour_demands,training_capacities. Permanecem275fichas/270instituições publicadas. Recibo `outputs/education-review-preparation-20261111/installation.json`; pacote preservado `outputs/entrega-educacao-retoma-20261111.zip`, SHA256 `d2b7fd5e0d24baffd7b53d534c78e6e0f4492b47a40bb1d9d146b8a8b553caf5`.
Este recorte deixa de ser pendência de implementação e instalação. Conferência visual continua pendente; jornadas externas educativas e representação/consentimento documentados permanecem em aberto. Resíduos fiscais L1–L7/restanteL8/L9–L16 e restantes módulos mantêm-se no saldo existente. Próximo trabalho: primeiro residual executável dessa ordem, sem repetir este recorte nem as provas aceites.
## 11/11/2026 — retoma durável da revisão de Educação em QA
A revisão interna de procura e capacidade pode guardar e recuperar a preparação privada, incluindo notas, versão analisada e intenção de decisão incerta. Reutiliza os eventos de auditoria existentes, com conteúdo cifrado e isolamento por conta, tipo e declaração. Guardar não decide: a decisão continua pelo escritor nativo, com revisão independente e validação de versão. A intenção fica persistida antes do envio; uma resposta perdida recupera a mesma chave, sem duplicar a decisão. Edições posteriores e preparações mais recentes noutra janela não são substituídas silenciosamente.
**QA concluído, ainda não instalado:** cinco testes nativos/105 asserções e dez testes novos de interface, com os 17 afectados preservados; análise PHP e tipos aprovados. A página nativa recuperada foi exercitada no componente real em harness: recibo reconhecido, edição posterior conservada e zero envios adicionais. Prova `outputs/education-review-preparation-20261111/verification.json`. Isto não constitui conferência visual no navegador. Sem migração, tabela, papel ou concessão nova.
**Saldo:** falta entregar este delta pelo processo Git e conferir a apresentação no uso normal. Jornadas externas de estudantes, formadoras e empregadores, representação e consentimento documentados permanecem pendentes; Educação não é declarada integralmente concluída. A homologação mantém `9c067d6a` e as 270 instituições publicadas. Permanecem explícitos os resíduos fiscais L1–L7/restante L8/L9–L16 e os restantes módulos. As provas aceites não foram reabertas.
## 11/11/2026 — combinação CBF15/créditos/PEC instalada em9c067d6a
Instalação expressamente autorizada nesta retoma e executada pelo processoGit existente: `9c067d6ae89c0cd1e9ad4f2813e84048988d3319`, conferida às07:43:38Africa/Johannesburg(05:43:38UTC). Dezficheiros do delta conferidos por hash; revisão,entrada,login,up,Concursos,fundos e simuladorHTTP200; configuração preservada, zero migrações/importações. Backup/restauro isolado conferido:190tabelas e369ficheiros. Nenhum restauro sobre dados activos.
**Utilizável na homologação:** combinação CBF15corrente/transportado com créditos económicos/internacionais e saldos anteriores, com escolha dePEC limitada à colecta residual apósCBF15; gravação/recuperação e relatórios pelo percurso existente, conforme cinco cenáriosQA aceites. Os recortes anteriores de transporte e os históricos são preservados. Nenhum saldo oficial é consumido, pagamento/reembolso executado ou nova prioridade entre exercícios inventada.
**Preservação activa comprovada:** totais e checksums idênticos ao backup nas tabelas `funding_programmes`, `funding_programme_versions`, `funding_programme_interests`, `desk_cases` e `simulation_form_drafts`.275fichas,270publicadas; o original financeiro continua disponível. Não se extrapola esta comparação para tabelas voláteis de sessão/cache. Prova `outputs/irpc-cbf15-combination-20261111/installation.json`; pacote e reversão `outputs/entrega-irpc-combinacao-20261111.zip`, SHA256 `cebcd063b06f70a33b57f6cdd710478fb7aa3d18ef64aec4489cf855064bff99`.
Saldo: este recorte deixa de ser pendência de implementação/instalação. Conferência visual mantém-se separada; continuam repartição automática/múltiplos projectos, outros períodos/moedas/regimes/EP/cumulações/benefícios/formulários e os restantes resíduos L1–L7/restanteL8/L9–L16 e módulos não fiscais. O portal não é declarado integralmente concluído. A próxima implementação seguirá o primeiro residual executável do saldo existente, sem reabrir a combinação entregue nem a publicação das270instituições.
## 11/11/2026 — combinação CBF15 com créditos económicos/internacionais e PEC em QA
Retoma da ordem de conclusão existente, sem nova auditoria ou repetição da publicação financeira:270instituições publicadas e revisão instalada4f3dbcf8 preservadas; visual financeiro/S08 permanece separado. Reconciliação delimitada: o transporte CBF15 básico(7476dc34) já integra a revisão instalada4f3dbcf8, pelo histórico Git e recibos aceites; a nota antiga «QA, não instalado» desse transporte foi superada por entregas posteriores. Não reinstalar esse recorte como se estivesse ausente.
**Implementado nesta passagem, ainda sem instalação:** retirada a recusa técnica genérica da combinação de CBF15 corrente/transportado com saldos anteriores, reutilizando as fontes e ordem de dedução existentes. Créditos económicos e internacionais/correspondentes transportes precedemCBF15; PEC corrente e anterior usam apenas a colecta residual seguinte, antes de retenções/PPC. Uma escolha explícita de PEC superior ao residual é recusada como indeterminada, sem reduzir silenciosamente parcelas ou fabricar saldo recuperável. Histórico e fontes de investimento/uso anterior continuam preservados; a chave da combinação inclui versão própria para exigir recálculo dos resultados anteriores desse ramo, sem reescrever históricos nem invalidar os recortes sem combinação.
Cinco cenários sintéticos: combinação ordinária138MZN; quota residual documental100→saldo438; PEC sobrealocado→saldo indeterminado; CBF15 esgota colecta1338 ePEC seleccionado expressamente0→saldo−500 proveniente dePPC, sem reembolso/consumo oficial; janelaCBF15 expirada→saldo538. Também provada a varianteCBF15 corrente e o limite de quota após créditos anteriores. Cálculo→POST/PUT do rascunho privado existente→GET→recálculo exacto→HTML/dossier aprovados no QA55441/proactive_investment_20261027_test, com rollback e sem novas bases/portas/migrations/direitos. Uma provaAPI/37asserções; cinco testes novos nas combinações e23casos afectados de compatibilidade aprovados, não multiplicados como funcionalidades. Tipos/lint aprovados; compilação QA e pacote registados no fecho. Provas `outputs/irpc-cbf15-combination-20261111/*-verification.json` e relatórios recuperados.
**Saldo:** implementação e percurso desta combinação concluídos emQA; instalação do novo delta e visual separados. Persistem repartição automática/múltiplos projectos, outros períodos/moedas/regimes/EP/cumulações/benefícios e documentos fora dos recortes demonstrados; L1–L7/restanteL8/L9–L16 não são encerrados. Os cinco programas anteriores privados do directório financeiro não são publicados por consequência desta entrega. Próximo trabalho: agrupar a entrega compatível deste delta e continuar o primeiro residual fiscal executável, preservando os fechos aceites.
## 10/11/2026 —270instituições publicadas e consultáveis na homologação
**Cadastro e disponibilização do documento fornecido concluídos em homologação.** Conferência de leitura às22:54:31Africa/Johannesburg(20:54:31UTC), revisão instalada `4f3dbcf86499180011fc79712d6b5be445e57e26`:270/270instituições/operadores do documento publicados, nenhuma referência do original em falta,270identidades únicas,393referências físicas e168contactos. Publicações confirmadas pelo fluxo editorial nativo do utilizador; zero escritas efectuadas pelo verificador. As275fichas totais incluem cinco referências anteriores de fundos/programas que permanecem privadas, sem serem publicadas por consequência deste lote. Fonte original/SHA/edição, incertezas e identidades preservadas.
Consulta pública comprovada porHTTP200 e dados efectivamente devolvidos: `/fundos?kind=institution&include_historical=1` contém270resultados; filtro categoria seguros/tipo seguradora/presença física/Nampula contém seis(Fidelidade,Emose,HollardMoçambique,Britam,Arko,Palma). Fichas deBCI,Absa eArko abrem no componente público e conservam a fonte SHA256 `119bb5514d2777849b70aebfec21d28a41885cbdc7d9827fbebd764fe994c108`. A opção de incluir histórico não transforma uma instituição fundida/extinta em activa. Sede/balcões continuam separados de cobertura e produtos; dados desconhecidos não se tornam condições confirmadas nem garantias de financiamento/seguro.
**Saldo ACT-20261029-12 neste recorte:** conteúdo do documento cadastrado, publicado pelo editor e disponível por localização/categoria/tipo; já não é página vazia ou apenas códigoQA. Continuidade banco/seguradora/balcão/fonte/versão no mesmo dossier, gravação/recuperação e conservação após alterações continuam cobertas pelas provasQA aceites, reutilizadas sem criar dossier sintético em produção. Conferência visual de navegador permanece separada; não se presume instalação de outros módulos, fecho integral do portal ou conclusão dos resíduos fiscais. Nenhuma nova instalação/alteração de direitos exigida para declarar este resultado. Prova `outputs/funding-directory-20261110/institutions/publication-complete.json`.
## 10/11/2026 — recusa depois do login: navegadores e sessões diferentes
URL comunicado pelo utilizador:`/dashboard`. Conferência dirigida dos registos confirma login/MFA no **Edge** às22:03:53, seguido de`/dashboard`HTTP200 às22:03:55. A abertura seguinte de`/management/fundos/referencias-assistidas`às22:04:02/22:04:17 recebeu403 em **Safari/WebKit**, não no Edge. Existiam duas sessões da mesma conta: a sessão recente do Edge com confirmaçãoMFA válida e a anterior com confirmação antiga. A autorização editorial/verificação de email/perfil existentes estavam preservados. Logo esta nova recusa não comprova expiração do MFA recém-concluído no Edge nem falta de permissão da conta; o link abriu outro contexto de navegador.
Solução indicada: copiar e colar o URL do editor na barra do Edge já autenticado, mantendo a mesma aba nas retomas. Não repetir login por rotina, alterar permissões, desactivar verificações ou reinstalar por este motivo. **Reposição de acesso confirmada:** GETdo editor noEdge às22:11:21(+02),HTTP200. A recusa de entrada ficou delimitada e resolvida por uso da sessão correcta, sem nova instalação, concessão ou alteração de segurança. A instalação4f3dbcf8 permanece; publicação/conteúdo consultável ainda não declarados concluídos. Consulta estritamente de leitura; nenhum identificador de sessão/token/cookie/palavra-passe exportado e nenhuma sessão modificada. Prova:`outputs/funding-directory-20261110/institutions/access-context-diagnosis.json`.
## 10/11/2026 — retoma editorial instalada em4f3dbcf8
Correcção instalada pelo executor Git existente em `4f3dbcf86499180011fc79712d6b5be445e57e26`, confirmada às22:01:41Africa/Johannesburg (20:01:41UTC).7ficheiros conferidos por hash; revisão/entrada/login/up/Concursos/fundos HTTP200, configuração preservada, zero migrações/importações automáticas. Novo backup/restauro190tabelas/369ficheiros conferido, sem substituir dados activos. Pacote `outputs/entrega-instituicoes-retoma-20261110.zip`, SHA256 `9bce0250d546cd87f7c60a55f0c1d47fba74e8965f7d3224a588e140f87ec3cb`; anteriores preservados.
**Saldo activo:90rascunhos, zero publicações.** A indicação de publicação não foi confirmada no servidor por expiraçãoMFA/403. A correcção torna visível o estado e conserva os recibos na mesma aba, poractor/fontes; não publica por consequência da instalação nem altera o limiteMFA900s. Os90rascunhos anteriores ao armazenamento no cliente são recuperados idempotentemente na primeira passagem actualizada; após isso as fichas conferidas são saltadas nas retomas da mesma aba. Confirmações de revisão/publicação voltam a ser exigidas na entrada, e o backend revalida guardas/hash/versão. Fechar a aba/limpar armazenamento não elimina rascunhos, mas perde a retoma local. Estado alterado por outra janela exige conferência actual; o armazenamento do cliente não é prova de publicação.
Resta a conclusão autenticada da preparação/publicação e a consulta efectiva do conteúdo no destino. ACT12 continua aberto, visual pendente, sem reabrir APIEX/SWOT/S08 ou resíduos fiscais anteriores. Prova: `outputs/funding-directory-20261110/institutions/resume-installed.json`.
## 10/11/2026 — retoma após MFA, sem repetir fichas conferidas
Verificação activa posterior:90rascunhos/zero publicações; continuação GETMomentum403 às21:49:39 ePOSTpublicar403 às21:49:54 (+02). A sessão voltou a expirar. A publicação comunicada não chegou a ser executada pelo servidor. Nenhum rascunho foi removido.
Correcção delimitada emQA: a mesma aba conserva selecção, recibos/versionamento/hash e fundamento, separados por conta e conjunto de fontes; após reautenticação salta fichas já conferidas. As confirmações de revisão/publicação são desmarcadas na nova entrada, sem publicação automática. Uma publicação de resposta incerta conserva o mesmo corpo para recuperação idempotente. Novo comando de renovação utiliza logout/login/MFA nativos; limite900s e guardas mantidos. «Seleccionar todas as instituições(270)», contagem de progresso e controlos antes da lista recolhida tornam o estado visível. Sem migração, dados novos ou alterações de permissões. Alteração ainda não instalada nesta anotação; revisão activaef24cbdc.
Prova dirigida do componente efectivo: remonta a página como após autenticação, recupera selecção/recibos, não repete preparação, não publica automaticamente, recupera confirmação incerta e isola outra conta/fontes. Prova API editorial anterior1caso/27asserções reexecutada apenas pela propriedadeeditor_id acrescentada; provas270fichas e do dossier reutilizadas. Tipos/análise/compilação da entrega são registados pelo executor existente.
## 10/11/2026 — confirmação da preparação parcial e expiração do MFA
Após execução no editor, a homologação contém **32rascunhos e zero fichas publicadas**. A preparação gravou efectivamente e avançou por diferentes instituições. Os registos HTTP mostram preparação200 atéMyBucks às21:31:42, seguida deGETxtenda403 às21:31:43 ePOSTpublicar403 às21:31:59 (Africa/Johannesburg). A entrada no editor fora confirmada às21:16:43; corresponde ao limite existente de900segundos do MFA em `PortalProfileAccess::mfa`. Não é perda da instalação nem repetição de dados. Reautenticar no portal com MFA e retomar a preparação idempotente conserva os32rascunhos, sem substituir edições; publicação continua a exigir a confirmação separada. Nenhuma guarda, sessão ou permissão foi alterada. Revisão instalada `ef24cbdc`, ACT12 ainda sem conteúdo público confirmado. Prova de leitura: `outputs/funding-directory-20261110/institutions/publication-observation.json`.
## 10/11/2026 — instalação confirmada do directório financeiro
Revisão `ef24cbdc9218e2027f7d1a39968cc60869a35410` instalada na homologação às19:15UTC, pelo executor Git existente.33ficheiros conferidos por hash com o commit; configuração preservada, zero migrações/importações automáticas. Backup/restauro confirmado de190tabelas e369ficheiros. Entradas `/fundos?kind=institution`, `/concursos`, `/login`, `/up` e revisão HTTP200. Pacote anterior preservado; nova entrega `outputs/entrega-instituicoes-editorial-20261110.zip` SHA256 `168d7ffb6d054533674f3f07799959b46bc12641c045accfc1766da5cead9077`.
**Implementação instalada; conteúdo ainda sem publicação confirmada.** Leitura activa:zero fichas/zero publicações; tabelas sem RLS, logo o resultado não decorre de falta de contexto de leitura. Entrada autenticada no editor assistido confirmada nos registos; ainda sem pedidos POST de preparação/publicação observados. A indicação do utilizador de operação concluída permanece por reconciliar com o resultado efectivo, sem repetir a importação ou simular MFA. A conta existente tem designação editorial e perfil exigidos; nenhuma concessão alterada. ACT-20261029-12 permanece aberto até existirem fichas consultáveis. Visual e restantes resíduos do portal mantidos.
## 10/11/2026 — preparação editorial assistida das instituições
A autenticação SSH foi confirmada, mas não substitui a sessão e MFA recentes do editor no portal. Acrescentada `/management/fundos/referencias-assistidas`: selecção explícita das instituições do documento, conferência das fontes, preparação sequencial idempotente e publicação confirmada numa segunda operação. Preparar nunca publica. Fichas deliberadamente alteradas são preservadas e exigem revisão individual; mudança do conjunto de fontes interrompe a operação. Uma resposta de publicação perdida conserva versão/hash/fundamento para a mesma repetição. Expiração de MFA mantém as guardas e os resultados já confirmados.
Prova adicional delimitada:1caso API/27asserções,1caso frontend de separação preparação/publicação e recuperação da confirmação. Reutilizadas as5provas API/735asserções e28frontend anteriores; não se repetiu a carga das270instituições. Instalação e publicação no destino continuam distintas e serão registadas após confirmação efectiva. Sem migrações, novos direitos ou alteração das sessões existentes.
## 10/11/2026 — instituições por localização, categoria e tipo no processo do investidor
Pedido expresso da Tempo: inserir as instituições do documento agora indicado por localização/categoria/tipo e permitir que o investidor consulte bancos, seguradoras e outros intervenientes quando a necessidade surgir, sem limitar a decisão por falta de informação. **Implementação e conteúdo demonstrados em QA; nenhuma instalação ou publicação activa executada nesta passagem. Última revisão instalada confirmada:1c75a30a, APIEX/SWOT/Concursos preservados. ACT-12 não é encerrado pela preparação técnica.**
Original read-only `Notas/Instituições financeiras em Moçambique.docx`, edição Novembro2026, SHA256 `119bb5514d2777849b70aebfec21d28a41885cbdc7d9827fbebd764fe994c108`. Texto,509linhas de tabelas e nomes/contactos presentes nos parágrafos reconciliados. **270instituições/operadores,393referências de localização física e168referências de contacto**, não270ofertas de financiamento nem393moradas independentemente confirmadas. Instituições repetidas são reunidas por identidade explícita/aliases documentados ou número de registo citado; divergências, moradas alternativas, terceiros, dados históricos e identidades por confirmar permanecem identificados, sem substituir factos. Entidades sem morada continuam consultáveis como não localizadas. Totais estatísticos/redes incompletas não criam entidades ou agências fictícias. Listas antigas não confirmam licença actual; ABC permanece fundida/extinta segundo o documento, ligada à referência Access, sem atribuir a origem dos balcões por inferência.
Reutilizado o catálogo/versionamento/editorial existente: categorias banca,crédito/microfinanças,gestão de investimento,pensões,seguros/resseguros,mediação,câmbio,pagamentos e supervisão; uma entidade pode ter mais de uma categoria/função sem duplicação. Filtros por categoria,tipo e presença física são distintos da cobertura do produto. Província do cadastro é associada apenas por correspondência inequívoca; rua/localidade e precisão limitada conservam-se como declaradas. Pesquisa por cidade/balcão/morada/alias permite localizar informação textual (por exemplo Beira), sem geocodificação inventada. Informação por confirmar não é correspondência confirmada; ausência de registo não prova ausência de oferta. Oito gestoras de pensões continuam distintas de produtos de crédito, e seguradora não é apresentada automaticamente como financiadora/garantia de crédito.
**Continuidade no mesmo dossier:** ferramentas do investimento abrem a pesquisa contextual; seleccionam-se necessidade (incluindo seguros), instituição e, opcionalmente, sede/balcão. Guarda-se identidade estável, categorias/tipos, versão pública, local exacto escolhido, fonte/documento/SHA/linha, estado de confirmação e produto quando existente. Banco e seguradora coexistem; gravação repetida não duplica escolha. Actualização posterior dos pressupostos do investimento conserva a escolha; retirada da ficha pública não apaga a fotografia consultada. Dados/etapas/autoridade de decisão anteriores não são alterados. Consulta pública livre, operações privadas e edição editorial com as guardas existentes; nenhuma candidatura, email ou contacto enviado às instituições, novo perfil/grant/porta/schema ou ligação fictícia.
Provas no QA55441 existente, com rollback: **5casos API/735asserções únicas** (2desta fonte/608 e3de compatibilidade/127),28frontend dirigidos incluindo S08/navegação e recuperação da tentativa com balcão escolhido, tipos/PHPStan/lint/Pint/compilação QA aprovados. Uma tentativa de suite paralela foi recusada pela guarda da mesma base; compatibilidade executada sequencialmente sem remover a guarda ou criar outra base. `outputs/funding-directory-20261110/institutions/{content,verification}.json` e **`catalogo-qa.html` com270fichas ligadas**; renderização estática dos componentes, não conferência visual de navegador. Provas e pacote anteriores das11fichas/BNI Capital/FDEL mantidos. As7instituições comuns são enriquecidas pela mesma identidade; não há segundo directório. Fontes/documento original não foram alterados.
**Saldo concreto:** conteúdo e percurso local/categoria/tipo → banco/seguradora → fonte/contacto → mesmo dossier → continuação/recuperação demonstrados em QA. Restam instalação e disponibilização/revisão/publicação autenticada pelo fluxo editorial existente, consulta efectiva no destino e visual (conector indisponível). Preparação documental não publica lote automaticamente, conformeAGENTS.md; não declarar informação já disponível na homologação por existir este código. Restantes módulos e resíduosL1–L7/restanteL8/L9–L16 mantêm o saldo anterior.
## 10/11/2026 — ACT-20261029-12: directório financeiro com conteúdo em QA
**Homologação mantém1c75a30a; este conjunto não está instalado nem publicado.** A leitura autenticada do destino confirmou catálogo /fundos HTTP200, schema instalado e zero fichas/versões. O requisito não fica encerrado por essa disponibilidade técnica. APIEX/SWOT mantêm a entrega instalada própria, sem reabertura.
Localizada e reutilizada a lista fornecida em `Relatorios/benchmark de melhorias investeMoz.docx`, SHA256 `065203aacfae766c62cf9c2c272ebacc61ff23bd4ff9e91535939fede87f7617`, quadro de04/10/2026. Doze entradas documentais correspondem a **11fichas:7instituições e4programas/fundos** — BCI,Millennium bim,Absa,Standard Bank,Moza,BNI,Gapi,FECOP,Semeia Negócios,FCID VIII eFAE; **BNI Capital** integra os produtos do BNI, sem duplicar instituição ou presumir personalidade jurídica autónoma. Agregado genérico de linhas BNI não gera fundos fictícios. Referência FDEL anterior conservada, sem a contar novamente nesta entrega.
O cadastro/versionamento/competência editorial existentes passam a conservar natureza factual, identidade estável, contactos com fonte/data, locais físicos (sede/balcão/ponto institucional) e produtos próprios. Moradas não determinam cobertura financeira. Produto tem finalidade, instrumento, condições/montantes, disponibilidade e cobertura próprios; vazios são identificados como por confirmar. Cobertura e necessidade têm de corresponder ao **mesmo produto**, antes da paginaçãoSQL. Critério documentado pode excluir desconhecidos; consulta geral distingue resultados por confirmar de correspondência sustentada. FCID conserva seis províncias documentadas, sem confundir Maputo Província/Cidade; FAE mantém o Vale do Zambeze como âmbito textual com vínculo geográfico por confirmar, sem inventar distritos. A localização institucional do BNI não limita o financiamento a Maputo. Semeia conserva a chamada encerrada; a antiga linha BCI2018 não é reaberta. Condições e janelas históricas não se tornam ofertas actuais pela importação. Fontes indicadas consultadas para âmbito/disponibilidade; falha de recolha da Gapi não revoga o original, cuja data04/10 e disponibilidade por confirmar ficam explícitas.
Demonstrado em QA55441 existente: localização/necessidade → consulta das fichas reais fornecidas → fonte/contacto/condições → escolha privada no mesmo dossier e oportunidade → recuperação. Snapshot preserva versão pública e produto consultados mesmo após retirada/alteração da ficha. Repetição da intenção e recuperação após recarregar não duplicam a escolha/evento; outra oportunidade/actor e dossier encerrado mantêm guardas. Não envia candidatura, contactos ou dados às instituições, não concede crédito nem classifica o investidor automaticamente. Sem pontuação/IA, schema/base/porta nova, novas competências ou alteração de dados activos.
Provas dirigidas: **3casos API/127asserções e13frontend**, PHPStan nível7 sem supressões, tipos/lint/Pint/compilação QA aprovados. Testes transaccionais revertidos; sequências QA podem avançar. `outputs/funding-directory-20261110/verification.json`, `content-qa.json`, `dossier-qa.json`; **catálogo consultável de revisão** `outputs/funding-directory-20261110/catalogo-qa.html` e11fichasHTML ligadas. Renderização estática dos componentes afectados, não conferência visual de navegador nem publicação no destino. Provas anteriores preservadas e reutilizadas.
**Saldo ACT-12:** recorte funcional e conteúdo fornecido demonstrados em QA, preparado para disponibilização pelo fluxo editorial. **Restam instalação do conjunto, preparação/revisão/publicação autenticada das11fichas no destino e confirmação da consulta real pelo investidor**; visual continua pendente com conector indisponível. Não declarar ACT-12 encerrado ou o portal integral. Dados de sede/balcões, condições ou cobertura não documentados conservam o estado por confirmar; não bloqueiam os restantes dados fundamentados. Resíduos L1–L7/restanteL8/L9–L16 e outros módulos mantêm o saldo anterior.
## 10/11/2026 — APIEX, SWOT e acesso destacado a Concursos instalados
**Homologação instalada e conferida: `1c75a30a601ef00fc187ddf5a64183446d329e6f`**, https://teste-investidor.tempo.co.mz. Inclui APIEX/SWOT37854d02 e correcção do menu autorizada pela Tempo. **Concursos e anúncios** tem agora grupo próprio no menu horizontal comum, com consulta pública e acompanhamentos para utilizadores autenticados; endereço https://teste-investidor.tempo.co.mz/concursos. O módulo já estava activo e apenas a entrada estava dentro de Descoberta; não foi necessário activá-lo novamente. Um rascunho privado e zero versões publicadas preservados.
Conferência final:21hashes Git iguais à release, revisão HTTPS correcta e root/up/login/concursos HTTP200, configuração intacta, zero migrations/importações. Backup fresco restaurado com190tabelas/contagens/checksums iguais e369ficheiros/configuração conferidos;7476dc34 e pacotes anteriores preservados para reversão de código. Recibos `outputs/apiex-swot-concursos-delivery-20261110/{backup,restore,files-restore,verification,delivery}.json`; verificação UTC no recibo. Instalação executada pelo mesmo executor Git, sem alterações de permissões, dados reais, publicação de análises ou submissão externa.
**Saldo:** APIEX e SWOT abaixo deixam de ser pendência de instalação. Acesso pelas ferramentas **Preparação APIEX e SWOT** do dossier existente, conservando a oportunidade seleccionada. Conferência visual no uso normal continua pendente no mesmo registo; não é inferida das provas automáticas. Âmbitos normativos não mapeados continuam indeterminados; os restantes módulos e resíduos L1–L7/restanteL8/L9–L16 conservam o saldo anterior. O portal não é declarado integralmente concluído.
## 10/11/2026 — correcção dirigida das âncoras antes da promoção
O executor recusou c7901592 por um caso frontendS08 (1falha/1788), mantendo7476dc34 activo. Diagnóstico delimitado: a página tinha dois efeitos de âncora e o teste existente conservava uma única sequência de dependências. A abertura SWOT foi integrada no listener de hash já existente, também para mudanças posteriores de #tarefas, sem alterar a guarda/foco/expansão de S08. O teste S08 aceite conserva as suas asserções; acrescentado caso que alterna #tarefas/#S08, verifica isolamento no dossier e limpeza do listener.15casos dirigidos APIEX/SWOT/âncoras aprovados. Nenhuma alteração de dados, permissões, fontes normativas ou configuração; entrega autorizada retomada no mesmo mecanismo, sem mascarar a falha nem desactivar verificações. Recibos finais continuam a determinar a revisão instalada.
## 10/11/2026 — Concursos visíveis no menu principal; instalação agrupada autorizada
Conferência delimitada do destino7476dc34 por SSH autenticado: `procurement.enabled=true`, módulo disponível, tabelas presentes; `/concursos` e raiz HTTP200 com `procurementEnabled=true`. Preservados um concurso em rascunho e zero versões publicadas. O módulo não desapareceu nem estava desligado: a ligação estava dentro de Descoberta. Pedido Tempo «onde foram parar concurso? depois de resolver isso autorizo a instalação»: entrada promovida a grupo próprio **Concursos e anúncios** no menu horizontal comum, com **Consultar concursos e anúncios** e, após autenticação, **Os concursos que acompanho**. Retirada só a ligação duplicada de Descoberta; cinco atalhos principais e autorizações editoriais preservados. Não cria/publica concursos nem muda flags, dados ou schema.
Prova dirigida da navegação:20casos aprovados, incluindo visitante/utilizador, disponibilidade, destinos únicos e selecção correcta de catálogo/ficha/acompanhamentos. Tipos/lint/compilação QA aprovados. Observação anterior `outputs/investment-preparation-20261110/concursos-before.json`; pacote agrupado `outputs/entrega-apiex-swot-concursos-20261110.zip`, preservando o pacote APIEX/SWOT anterior. O conjunto APIEX/SWOT abaixo conserva as provas aceites, sem repetição. Instalação autorizada expressamente após esta correcção; execução e recibo final separados desta prova QA. Visual continua pendente por indisponibilidade do conector.
## 10/11/2026 — APIEX e SWOT no dossier existente: QA concluído, instalação pendente
**Instalado:** `7476dc343098f15a7982e9d7581bf4f59d43bc1a` em https://teste-investidor.tempo.co.mz, promovido às14:28:12UTC e conferido às14:29:07UTC (16:29 locais). Transporte CBF15 deixa de ser pendência de instalação. Delta14hashes Git/HTTPS, root/up/login/revision200, configuração intacta, zero migrations/importações; backup/restauro190tabelas e369ficheiros conferidos. Recibos `outputs/cbf15-carry-delivery-20261110/`; release anterior e pacotes preservados.
**Implementado em QA, ainda não instalado — APIEX:** dossier existente → requisitos fundamentados disponíveis/em falta/não aplicáveis/indeterminados → documentos existentes e complementos → revisão → prévia/ZIP com índice e anexos íntegros → registo da entrega externa, esclarecimento, resposta e decisão com data/referência/comprovativo. Dados do investidor/empresa/contexto/localização/oportunidade reutilizados. Estados em preparação/pronto para entrega/entrega registada/decisão recebida distintos; exportar não submete nem aprova. Snapshot preparado, versões, SHA e pacote privado cifrado preservados; recuperação devolve os mesmos bytes após alteração do contexto, revalidando acesso e segurança dos documentos. Ausência de recepção electrónica APIEX, segundo a orientação Tempo, não bloqueia este percurso; nenhum conector fictício ou formulário oficial inventado. Reutiliza o avaliador e modelos/fontes aceites; períodos/regimes fora do âmbito actualmente mapeado ficam indeterminados, sem conclusão normativa presumida.
**Implementado em QA, ainda não instalado — SWOT:** no mesmo dossier/oportunidade, objecto explícito investimento/território/sector/cluster, evidências do contexto/análise proactiva e Competitividade pública aprovada, quatro dimensões internas/externas, fonte/data/classificação de cada conclusão (facto documentado/hipótese/por confirmar). Sem pontuação, IA ou inferência de ausência pela falta de registos. Gravação/recuperação/histórico paginado e revisão pela equipa com permissões existentes. Alteração/retirada de fontes assinala revisão, conserva fotografia consultada e edições deliberadas. Acções ideia/oportunidade/parceiros/risco continuam no mesmo dossier; risco liga à tarefa nativa com origem própria e repetição sem duplicação. Privado por defeito; publicação exige o fluxo editorial existente, nunca automática. Contextos retirados ficam no histórico privado de leitura, sem reactivação.
Provas sintéticas no QA55441 existente, com rollback: seis casos API/211asserções;16casos frontend dirigidos (incluindo transporte e cabeçalho comum147páginas), tipos, análise estática e lint aprovados. `outputs/investment-preparation-20261110/verification.json`, ZIP/índice/anexos, recuperação SWOT e provas de isolamento/histórico/segurança. Pacote Git agrupado `outputs/entrega-apiex-swot-20261110.zip`, base exacta7476dc34, reversão de código sem apagar auditoria, pacotes privados ou tarefas posteriores. Nenhuma migration/base/porta/conta real nova, submissão real ou publicação. Compilação QA separada; provas anteriores reutilizadas.
**Saldo concreto:** APIEX e SWOT acima encerrados em implementação QA; instalação deste conjunto e conferência visual continuam pendentes. O conector mantém “The browser access host is unavailable”; SSR/testes não substituem visual, sem novo ciclo de reinícios. Os restantes módulos conservam o saldo anterior, incluindo resíduos L1–L7, restanteL8 e L9–L16. Isto não declara o portal integralmente concluído.
## 10/11/2026 — transporte documental CBF15 em QA
Homologação mantém `d216830b`; este delta ainda não está instalado. Tipos/lint/Pint/buildQA aprovados; pacote com aplicação/reversão exactas,sem migrations/dados/credenciais. Pendência encerrada no recorte normal residente2026/ano civil/MZN: crédito de investimento de origem anterior do mesmo contribuinte/projecto/local/regime, com activo/documento/parcela, exercício de origem, NUIT/projecto de origem, taxa original e qualificação históricas documentados. Exercício de origem anterior a2026,dentro da janela original e não anterior à aquisição; no projecto em funcionamento,a aquisição não precede o início aplicável. Não renova o prazo nem recalcula origem a outra taxa. Valor corrente/ausência e uso efectivo agregado exigem reconciliação explícita; histórico de simulações não equivale a utilização fiscal.
Cálculo → POST/PUT do mesmo rascunho privado → GET → recálculo → HTML/dossier demonstrados no QA55441 existente com rollback. Exemplo sintético recuperado: nominal de origem4.000,uso efectivo anterior1.000,disponível3.000,quota documental do projecto200 → dedução200,não utilizado2.800,saldo500MZN. Início2022 → quinto exercício2026; mudando o início para2021 → sexto,parcela expirada3.000,disponível/dedução0,saldo700,com activo,uso anterior e origens preservados. Seis/sete resultados históricos conservados,incluindo o resultado ordinário aceite. Provas `outputs/irpc-cbf15-carry-20261110/{carry,expired}-verification.json` e relatórios respectivos; API um caso/28asserções novas,30casos frontend nas fronteiras alteradas incluindo formulário real compilado e prevenção de duplicação. SSR/formulário simulado não é navegador/visual. FonteCBF15(4/7),APIEX2025 PDF71–72 já aceite,reutilizada.
A preparação mantém os activos correntes e exige explicitar os dados em falta; activos de origem e correntes partilham a capacidade documental e a detecção de duplicação entre mapas. Origem com outro NUIT/projecto/taxa,exercício incoerente,uso superior ao nominal ou declaração de ausência incompatível não produz dedução. Sem pagamento,reembolso,consumo oficial ou criação automática de saldo transportado. Livros antigos permanecem nos formatos1/2; só o transporte preparado usa livro3/detalhe2,para o código anterior recusar em vez de ignorar o crédito novo. Snapshots e históricos aceites não são reescritos; reversão é de código e não deve sobrescrever dados posteriores.
Saldo preservado: outros projectos/localizações/regimes/cumulações/EP/L7,combinação positiva com transportes económicos/internacionais/PEC anteriores,repartição automática,benefícios/formulários oficiais e restantes resíduos L1–L7/restanteL8/L9–L16 mantêm os seus recortes. Os módulos não fiscais mantêm o saldo existente,sem nova auditoria geral. Próximo trabalho: primeira combinação residual executável respeitando a ordem de dedução e quota do projecto,ou percurso independente do saldo existente; visual continua pendente no mesmo registo. Não é conclusão integral do portal.
## 10/11/2026 — menus horizontais instalados
**Homologação: `d216830b068af8a7a40d43993795639e73aaed78`**, promovida às13:56:27UTC e conferida às13:56:54UTC (15:56 locais), em https://teste-investidor.tempo.co.mz. Delta10hashes Git e revisão HTTPS iguais; root/up/login/revision HTTP200, configuração preservada, zero migrations/importações. Backup/restauro190tabelas com contagens/checksums iguais e369ficheiros/configuração conferidos; revisão d5cb41b1 preservada para reversão. Recibos `outputs/horizontal-menu-delivery-20261110/{backup,restore,files-restore,verification,delivery}.json`. Pacote anterior e dados/históricos preservados; sessãoSSH pronta para continuação autenticada, sem credencial em disco.
O recorte dos menus horizontais abaixo deixa de ser pendência de instalação: cabeçalho comum em páginas públicas/autenticação/definições/privadas, sem sidebar global concorrente; Descoberta sem repetição dos cinco atalhos mantidos na entrada principal. Prova estrutural dos146layouts e23casos dirigidos reutilizada; não é conferência visual. O conector continua indisponível,sem novos reinícios ou alterações ao navegador. Visual mantém-se pendente no mesmo registo. O restante saldo de funcionalidades por módulo e L1–L7/restanteL8/L9–L16 não é encerrado por esta instalação.
## 10/11/2026 — janela CBF15 instalada; menus horizontais uniformizados em QA
**Homologação: `d5cb41b111cac95c431a3072b8c4cc0141481ec2`**, promovida às13:43:40UTC e conferida às13:44:18UTC (15:44 locais). Delta12hashes Git e revisão HTTPS iguais; root/up/login/revision HTTP200, configuração preservada, zero migrations/importações. Backup/restauro190tabelas e369ficheiros/configuração conferidos,89e6d72c preservada. Recibos `outputs/cbf15-window-delivery-20261110/`. Janela de cinco exercícios CBF15 deixa de ser pendência de instalação; transporte de créditos anteriores e restantes resíduos fiscais continuam separados.
**Delta posterior em QA, ainda não instalado — pedido expresso da Tempo10/11, corrigido para menus horizontais com dropdown:** cabeçalho comum passa a ser montado uma vez pelo layout global em todas as páginas; área privada usa o mesmo cabeçalho sem navegação lateral concorrente. Autenticação, definições, gestão e páginas públicas conservam conteúdo e breadcrumbs; filtros de capacidades, direitos e destinos privados existentes preservados. Esconder cabeçalho na impressão mantém-se. Os cinco atalhos (Porquê investir,Partir de uma ideia,Consultar oportunidades,Simulador Fiscal,Sectores) ficam intactos na entrada principal. Descoberta conserva localizações/mapa,catálogos e módulos opcionais,sem repetir os atalhos; Partir de uma ideia já estava fora desse grupo. CSS responsivo e implementação comum dos dropdowns/teclado/Escape/toque reutilizados,sem novo menu alternativo.
Conferência estrutural dos layouts das146páginasTSX existentes: exactamente um cabeçalho comum por composição, incluindo páginas privadas,autenticação/definições, sem sidebar global; conteúdo/contexto/breadcrumbs e cinco atalhos preservados. Provas `tests/frontend/portal-horizontal-shell.test.mjs`, navegação e renderização dirigida (23casos aprovados). É SSR com dependências de UI substituídas,não prova visual de navegador. Tentativa única de inventárioCUA nesta continuação devolveu exactamente `Browsers: Error: The browser access host is unavailable`; nenhuma alteração/reinício de navegador. Conferência visual mantém-se pendente no registo existente. Tipos/lint/build QA aprovados, logs desta entrega; base activa/dados/permissões intocados.
O saldo integral dos restantes módulos e L1–L7/restanteL8/L9–L16 permanece no plano existente; nenhuma contagem de páginas/testes declara o portal concluído.
## 10/11/2026 — janela de cinco exercícios CBF15 em QA
Homologação mantém `89e6d72c`; o delta seguinte ainda não está instalado. Pendência encerrada em QA: contagem inclusiva dos cinco exercícios civis do CBF15(4/7) na liquidação2026, para investimento efectivo do próprio2026. Início de exploração para projecto novo; início dos procedimentos após aprovação para projecto em funcionamento. Não usa cinco anos móveis nem reinicia o prazo pela data de aquisição. Início2022, mesmo em31/12 → quinto exercício2026, dedução200 pela quota residual, saldo500MZN. Início2021 → sexto exercício2026, prazo expirado, dedução0 e saldo700; activo e documento preservados. Datas inválidas/futuras e activos antigos apresentados como investimento corrente permanecem indeterminados/recusados, não zero por omissão.
Cálculo → POST/PUT do mesmo rascunho privado → GET → recálculo → HTML/dossier demonstrados no QA55441 existente, com rollback, cinco/seis resultados históricos e todos os anteriores preservados. Provas `outputs/irpc-cbf15-window-20261110/{fifth,expired}-verification.json` e relatórios respectivos. Modelo `IRPC-CBF15/2`, livro existente versão2 sem migration; snapshots aceites não são reescritos. Fonte já aceite APIEX2025 PDF71–72,CBF15(4/7), reutilizada sem nova validação de autenticidade. Provas anteriores reutilizadas; apenas fronteiras alteradas conferidas.
Saldo: transporte de créditos de investimento de anos anteriores, repartição automática/múltiplos projectos, EP/L7/cumulações/outros benefícios/formulários oficiais continuam por implementar nos respectivos recortes. A janela corrente não declara o transporte entregue. Resíduos L1–L7, restanteL8 e L9–L16 e saldo dos módulos não fiscais preservados. Conferência visual pendente no mesmo registo; portal não integral. Próximo trabalho executável: transporte documental CBF15 dentro da mesma janela, com consumo anterior reconciliado e sem duplicar o investimento corrente.
## 10/11/2026 — CBF15 instalado na homologação
**Revisão instalada: `89e6d72c1e86df8eb86e7be74583659975585036`**, promovida às13:27:59UTC e conferida às13:28:21UTC (15:28 locais), em https://teste-investidor.tempo.co.mz. Os17hashes do delta coincidem com Git; revisão/root/up/login HTTP200, configuração preservada, zero migrations/importações. Backup fresco/restauro190tabelas com contagens/checksums iguais e369ficheiros/configuração extraídos e conferidos;7c8d4726 preservada para reversão. Recibos `outputs/cbf15-delivery-20261110/{backup,restore,files-restore,verification,delivery}.json`; pacote imutável `outputs/entrega-cbf15-20261110.zip`, SHA25613bdecef2697edcc821d6fa490586d0c2f4b171b4308c655aa60ba5632664c83.
O recorte CBF15 abaixo deixa de ser pendência de instalação: primeiro exercício2026, residente geral/MZN, activos e condições documentados, colecta integral do projecto ou quota residual documentada, cálculo/gravação/recuperação/relatórios com histórico. As notas QA anteriores ficam como histórico das provas aceites. Outros exercícios/transporte/caducidade, repartição automática/múltiplos projectos, EP/L7/cumulações/outros benefícios/formulários e restantes resíduos L1–L7/L8/L9–L16 permanecem no plano existente. Próximo trabalho: residual executável de CBF15/L8, preservando o recorte instalado e sem repetir as provas aceites. Conferência visual continua pendente no mesmo registo; portal não declarado integral.
## 10/11/2026 — acordo PT–MZ instalado; crédito CBF15 demonstrado em QA
**Homologação: `7c8d4726e9f93325480e2ee6e5e790e9adbcaf19`**, promovida às12:36:07UTC e conferida às12:37:11UTC (14:37 locais). Revisão HTTPS/10hashes iguais, quatro entradasHTTP200; configuração preservada, zero migrations/importações. Backup/restauro190tabelas e369ficheiros/configuração conferidos, b10b35f3 preservada. Recibos `outputs/cdt-agreement-delivery-20261110/`. Falha inicial de recepção Git delimitada ao directório objects/7c pertencente aroot: apenas proprietário/grupo ajustados a conta do serviço,modo0755 inalterado; pedido original validado recolocado sem alterar corpo/SHA ou controlos. Recibos git-permission-repair/request-retry; não foi falha do portal nem alteração de segurança do navegador. Sessão SSH preservada para continuação autenticada, sem credencial em disco.
**Pendência efectivamente encerrada em QA, ainda sem instalação:** CBF15, crédito fiscal por investimento efectivo no primeiro exercício2026, residente geral/ano civil/MZN, colecta inteiramente da actividade do projecto ou quota residual documentada após créditos anteriores e sem outros benefícios próprios/cumulações ou transportes positivos combinados. Reutiliza qualificação CBF de L7 e fonte APIEX já aceite (recibo irpc-l7-common-qa-20261026,compilação oficial2025,PDF66–72,art15PDF71–72), não importação como activação. Localização do investimento,aprovação/condiçõesCBF3/9/10/regime genérico13,início aplicável e activos/documentos exigem correspondência própria. Activo novo corpóreo,nacional,produtivo e fora15(6); condições negativas/exclusões documentadas produzem crédito zero nessa linha,preservando os dados. Informação em falta não é zero.
Percurso completo: preparar detalhe → calcular após económico/internacional e antesPEC/RF/PPC → gravar/actualizar pelo writer privado existente → recuperar → recalcular → HTML/dossier com fornecedor/documento/parcela/valores/qualificação/período/histórico. Maputo cidade5%: investimento10.000 → crédito500 → saldo200 MZN. Restantes províncias10%: crédito1.000 → saldo−300, recuperável simulado devido aoPPC. Cenário de limite: nominal4.000,dedução1.400,crédito não usado2.600,PEC não utilizado200,PPC500; nenhum reembolso de crédito/PEC ou saldo oficial criado/consumido. Provas `outputs/irpc-cbf15-20261110/{city,province,capped,partial}-verification.json` e relatórios respectivos. Quatro versões do mesmo rascunho API em transacção revertida QA55441,com histórico anterior e versões seguintes preservados; zero alterações activas.
Quota residual documentada: nominal4.000, limite do projecto200, dedução200, não utilizado3.800 e saldo500 MZN; recuperada pela API com quatro resultados históricos. Quota superior à colecta global remanescente, negativa ou sem fundamento é recusada. Não há repartição automática de rendimentos/custos/perdas. Tipos, lint/Pint e compilação QA aprovados; provas anteriores reutilizadas.
Livros anteriores L8 permanecem versão1,com chaves/resultados inalterados quando sem CBF15. Só o livro com este novo detalhe usa versão2: cliente anterior recusa o formato,conservando o snapshot,em vez de recalcular ignorando o novo benefício. Não é migration de base. O resumo antigo dos benefícios fica conservado e não reaplicado; ausência dos restantes benefícios exige declaração/reconciliação explícita,mesmo quando CBF15 é ausente. Fonte documental tem capacidade por factura/parcela e entre mapas da entidade; mudar id/data não duplica investimento. Repetir simulação não é uso fiscal anterior.
**Saldo preservado:** CBF15 de outros exercícios/transporte/caducidade,repartição automática/múltiplos projectos,EP/L7/cumulações,outros benefícios e obrigações/formulários oficiais continuam separados. Ausência/zeroCBF15 não bloqueia os saldos anteriores aceites. Restantes L1–L7/L8/L9–L16 e módulos não fiscais mantêm o saldo existente; o portal não está integral. Visual permanece pendente. Conjunto CBF15 preparado para entrega sobre7c8d4726,sem instalação nesta entrada.
## 10/11/2026 — b10b35f3 instalado; acordo institucional PT–MZ em QA
**Homologação: `b10b35f30830922dae00afde50ac2bdb0e8cef6f`**, promovida às12:15:13UTC e conferida às12:16:31UTC (14:16 locais). Revisão HTTPS e16hashes Git iguais, quatro entradasHTTP200; configuração preservada, zero migrations/importações. Backup/restauro190tabelas com contagens/checksums iguais e369ficheiros/configuração conferidos;60b52fc8 disponível para reversão. Recibos `outputs/government-fraction-delivery-20261110/{backup,restore,files-restore,verification}.json`. Sessão SSH mantida aberta para continuação autenticada, sem credencial em disco. Isenção por devedor público11(3)a e fracções económicas L6 deixam de ser pendências de instalação; as anotações anteriores QA são históricas nesses recortes.
**Delta posterior QA, ainda não instalado:** correspondência11(3)b para instituição/organismo beneficiário corrente que concedeu o financiamento no âmbito de acordo concluído entre os governos PT–MZ. Beneficiário, financiamento ligado aos juros e acordo/cláusula aplicável exigem suporte próprio. País, nome da entidade ou qualificação institucional isolada não activam isenção. Fundamento distinto do devedor público11(3)a; limpar/trocar a escolha invalida a decisão corrente, sem apagar resultado histórico nem reutilizar campos ocultos como nova decisão.
Cálculo → gravação/GET pela API existente no QA55441 com rollback → recuperação → HTML/dossier demonstrados. Exemplo sintético: juros brutos60, imposto pago10 e fracção nacional8, com acordo qualificado → limite convencional0/crédito0/saldo708 MZN; sem reembolso, transporte ou consumo oficial e sem alterar L6. Relatório conserva instituição, financiamento e acordo. Prova `outputs/irpc-pt-mz-agreement-20261110/verification.json`; tipos/lint/Pint/build QA aprovados e provas anteriores reutilizadas.
Pacote `outputs/entrega-cdt-acordo-20261110.zip`, base exactab10b35f3, aplicação/reversão conferidas; entrega seguinte expressamente autorizada durante esta continuação, ainda sem instalação nesta entrada. Restantes beneficiários/regimes11(3)b, EP, relações especiais e demais CDT/benefícios/obrigações conservam correspondência própria; saldo L1–L7/restanteL8/L9–L16 preservado. Conferência visual continua pendente no mesmo registo; portal não integral.
## 10/11/2026 — CDT instalada; isenção governamental e precisão económica em QA
**Homologação: `60b52fc8c7446cbbee904a4411eca8ad284aa7ad`**, promovida às11:49:26UTC e conferida às11:50:11UTC (13:50 locais). Delta13hashes Git e revisão HTTPS iguais; portal/login/up/revision HTTP200, .env preservado, zero migrations/importações. Backup fresco/restauro190tabelas com contagens/checksums iguais e369ficheiros/configuração extraídos/conferidos; ac67f9c1 disponível para reversão. Recibos `outputs/cdt-pt-delivery-20261110/{backup,restore,files-restore,verification}.json`. Sessão SSH encerrada, credencial não guardada. Royalties/dividendos/juros ordinários PT–MZ deste recorte deixam de ser pendência de instalação; não reabrir provas aceites.
**Novos fechos dirigidos apenas em QA, ainda não instalados:**
- CDT11(3)a: devedor Estado português, subdivisão política/administrativa ou autarquia, expressamente qualificado e documentado; mantém residência, fonte, definição, período e restantes condições do recorte. Limite convencional0, imposto pago10 sem crédito elegível e saldo708 MZN no exemplo. Não gera reembolso/transporte nem altera L6. Beneficiários/acordos11(3)b e outras situações permanecem separados. Prova `outputs/irpc-pt-mz-government-20261110/verification.json`, com API/gravação/recuperação/HTML/dossier e fundamento do devedor no relatório.
- L6: diferença económica conserva fracções exactas, incluindo subcêntimos e n/d, pelo mecanismo racional existente. Remove a limitação técnica de exigir cêntimos inteiros neste mapa; não cria regra de arredondamento fiscal. Montante do tópico é reconciliado por igualdade racional, evitando tratar0,006 como0 ou aceitar truncamento. Exemplo de IRPC subjacente20,01: nominal12,006, efeito112,006, base4512,006, colecta1443,84192; reconferida L8, saldo723,83592. Repetição/substituição não acumula; valores já considerados/históricos preservados. Prova `outputs/irpc-economic-fraction-20261110/verification.json`, até API/recuperação/HTML/dossier/L8. Outros mapas e arredondamento de declarações oficiais conservam âmbito próprio.
Conjunto novo agrupável sobre60b52fc8; não integra a revisão instalada. Conferência visual permanece pendente no mesmo registo S08. Resíduos L1–L7, restante L8 e L9–L16 preservados; nenhum lote ou portal declarado integral. Próximos ramos: CDT11(3)b e restantes correspondências concretas, obrigações/benefícios e demais percursos conforme saldo existente.
Conjunto posterior preparado em `outputs/entrega-isencao-precisao-20261110.zip`, base exacta60b52fc8. Tipos/lint/Pint/build QA aprovados, aplicação/reversão conferidas em índices temporários. Não instalado; provas e pacotes anteriores preservados.
## 10/11/2026 — económico instalado; CDT Portugal avançada em QA
**Homologação: `ac67f9c13a6561b66c5da788a48849beac90a905`**, promovida pelo executor Git existente às11:32:14UTC e conferida às11:33:16UTC (13:33 locais). Revisão HTTPS e27hashes Git iguais; portal/login/up/revision HTTP200; .env preservado, zero migrations/importações. Backup/restauro190tabelas com contagens/checksums iguais e369ficheiros/configuração extraídos/conferidos. Release c359364d conservada. Recibos `outputs/economic-special-delivery-20261110/{backup,restore,files-restore,verification}.json`. Credencial não guardada; sessão SSH encerrada. Inclui quota económica, inclusão/reconciliação ordinária e especial L6, partilha64(3) e associações64(4). Entradas anteriores «apenas QA» são históricas nestes recortes; não reinstalar por consequência.
**Delta posterior, ainda QA:** correspondência CDT PT–MZ para royalties ordinárias não digitais, dividendos e juros ordinários de fonte portuguesa de residente convencional em Moçambique. País não activa a convenção. Exige beneficiário efectivo, definição/natureza, fonte/pagador, ausência de EP/instalação fixa ligada, período e suporte documentados; relações especiais verificadas nos juros/royalties, isenção governamental11(3) separada. Limite10% do bruto nos ramos suportados; dividendos usam alteração do Protocolo2009 art6, evitando taxa15% do texto1992. Não usa retenção L5 em sentido inverso nem tax sparing presumido. Mantém limite manual/históricos e parcela líquida nacional.
Prova sintética por ramo: bruto60 → limiteCDT6, imposto pago10/fracção nacional8 → crédito6 → saldo702 MZN. Cálculo/gravação/recuperação/HTML/dossier por API existente no QA55441; sem novo writer, schema, direito ou escrita activa. Provas `outputs/irpc-pt-mz-{credit,dividend,interest}-20261110/verification.json`. Fracções mantêm exactidão, sem arredondamento fiscal presumido. País/condição/período inválidos deixam apenas o caso dependente indeterminado. Este delta não integra ac67f9c1.
Restam CDT de outros Estados/naturezas, EP/instalação fixa, juros governamentais, excessos/relações especiais e casos fora do recorte, além dos demais resíduos L1–L7/L8/L9–L16. Conferência visual continua pendente no mesmo registo. Não declarar L8 ou o portal integralmente concluídos.
Conjunto CDT agrupado em `outputs/entrega-cdt-pt-20261110.zip`, base exacta ac67f9c1, aplicação/reversão conferidas; tipos/lint/Pint e build QA aprovados. Instalação deste delta ainda não executada.
## 10/11/2026 — crédito económico: partilha e associações em QA
Homologação mantém `c359364d`. Avanço posterior em QA, sem instalação: art64(3) distingue a parcela elegível do rendimento da partilha e o excesso com natureza de mais-valia; art64(4) distingue associação em participação e à quota, exigindo tributação efectiva documentada na participação. Reutiliza cálculo60%, prova do imposto subjacente, limites, prioridades, isolamento e writer privado existentes. Não calcula a mais-valia como crédito nem pressupõe inclusão L6 ou reconferência da colecta. Campos antigos e históricos são preservados; fontes incompletas ficam recuperáveis e indeterminadas.
Critério demonstrado: calcular → gravar/GET pela API existente no QA55441 com rollback → recuperar exactamente → HTML e relatório do dossier. Partilha sintética: valor atribuído200, entradas100 e rendimento documentado150 → elegível100/excesso50 → quota35,84/crédito12/saldo688 MZN. Associação documentada100/imposto20 → crédito12/saldo688 MZN, sem alterar L6. Provas `outputs/irpc-economic-liquidation-20261110/verification.json` e `outputs/irpc-economic-association-20261110/verification.json`. Não são actos fiscais nem consumo de saldos oficiais.
Conjunto de fontes compatíveis com quota e inclusão ordinária agrupado em `outputs/entrega-economica-especial-20261110.zip`, base c359364d, aplicação/reversão conferidas em índices temporários. Pacotes anteriores intactos. Inclusão/correcção especial em L6 também demonstrada em QA: reconcilia rendimento total da partilha (incluindo mais-valia) uma só vez, enquanto a quota usa apenas a componente elegível; associações reutilizam a qualificação própria. Prova outputs/irpc-economic-special-inclusion-20261110/verification.json: efeito162, base4562/colecta1459,84, quota35,84/saldo739,84 recuperados na API e relatórios. Casos transitórios, fracções de cêntimo, CDT, benefícios/obrigações e restantes resíduos L1–L7/L9–L16 continuam no saldo. Conferência visual pendente. Próximo trabalho funcional: correspondência CDT automática nos casos suportados pelas fontes existentes; eventual instalação deste conjunto é acção separada. Tipos/lint/Pint/build QA conferidos no conjunto final.
## 10/11/2026 — inclusão/correcção económica L6 em QA
Instalado: `c359364d`. Quota económica e inclusão/correcção L6 ordinária demonstradas em QA, sem instalação. Pacote anterior `outputs/entrega-economica-l6-l8-20261110.zip` preservado; novo delta posterior agrupado separadamente. Conferência visual pendente no registo existente.
Escolha explícita da origem L8, reconciliação de lucros/crédito já considerados, prévia da diferença e confirmação precedem o cálculo/gravação existentes. Repetição substitui a parcela; não acumula. Prova: +112 MZN leva lucro tributável a6812, base4512 e colecta1443,84. Montantes já incluídos100/12 dão efeito0; excesso150/20 corrige−58. Origem L8 anterior fica desactualizada; reconferida a nova colecta, quota35,84 e saldo723,84 mantêm-se na API/recuperação/HTML/dossier, com históricos preservados. Provas `outputs/irpc-economic-inclusion-20261110/verification.json` e `outputs/irpc-economic-quota-20261110/verification.json`. Tipos/lint/Pint/build QA aprovados.
Âmbito ordinário residente geral2026/MZN. Fracções de cêntimo exigem tratamento próprio, sem arredondamento presumido. Casos especiais, CDT e obrigações continuam no saldo. Resíduos L1–L7 e L9–L16 preservados; L6/L8 não integrais. Registos anteriores abaixo são históricos.
## Continuação integral — saldo e execução em 10/11/2026
Estado actualizado: conjunto Educação/fracção nacional ordinária instalado e HTTPS verificado em `c359364d`,com backup/restauro190tabelas/369ficheiros,delta14hashes,.env inalterado e zero migrations/importações. Recibos `outputs/education-l8-delivery-20261110/`. As anotações de QA/instalação pendente abaixo ficam históricas neste recorte. Conferência visual permanece separada.
Após essa instalação,avançado o residual de quota económica ordinária após acréscimo53:novo modo deriva de L6 com origem/versionamento e preservação do limite manual,salva/recupera pela API existente e apresenta HTML/dossier. Quatro casos novos+um API revertido/9asserções;tipos/lint/Pint aprovados. Prova `outputs/irpc-economic-quota-20261110/verification.json`. Este novo delta é QA,ainda não instalado. Inclusão/correcção automática do acréscimo na base e casos especiais não incluídos;L8parcial,resíduosL1–L7 eL9–L16 continuam explícitos. Não reabrir a fracção nacional internacional ou Educação para executar esse saldo.
Actualização L8 desta passagem: recorte ordinário de derivação da fracção nacional implementado e demonstrado em QA até gravação/recuperação/relatório pelo writer privado existente. Base4400/colecta1408/parcela100 → fracção32 → saldo simulado676,com limites/provas próprios; caso exacto2/625 e correspondência em falta demonstrados. Nenhuma determinação automática de CDT,repartição de custos/perdas ou consumo oficial. Modo documentado e históricos preservados. Delta agrupável com Educação,ainda sem instalação. Fonte e percurso no início do manual; provas `outputs/irpc-national-fraction-20261110/recovered-reports.json`.
Correcção do saldo global: preservar explicitamente resíduos L1–L7 e L9–L16 (INSS,IRPS mensal/anual,Selo,direitos,ICE,SISA,IPRA),conforme tabela actualizada na visão existente. Transporte/crédito económico entregues em569a1cd7 não são trabalho ausente. Nenhum lote agregado é fechado por número de testes;fecho comunicado nesta passagem é apenas a fracção nacional ordinária em QA.
Entrega agrupada de fontes: `outputs/entrega-educacao-l8-20261110.zip`, base ff726e57, manifesto e patch/reversão conferidos em índices temporários; Educação e L8 sem alterações de schema/rotas/writers. Tipos, lint, Pint e compilação QA finais aprovados. Sem instalação/publicação nova, sem alterar o pacote selado anterior ou o índice de trabalho. Homologação permanece ff726e57.
Instalação `ff726e57` concluída e verificada na homologação: contador/aliases, contactos, pré-dossier, curadoria, sitemap e S08 incluídos. As pendências de instalação dessas entregas nas entradas antigas abaixo são históricas, não trabalho a repetir. Prova: `outputs/reviewed-install-20261110/verification.json`. Conferência visual S08 conserva cinco pontos pendentes no mesmo registo; não bloqueia trabalhos independentes.
Saldo por percursos no início de [visao-consolidada-modulos.md](visao-consolidada-modulos.md), separado em implementação, instalação/conferência e dependência concreta. Matriz existente reconciliada em 35 IDs agregados, conservando 3.555 linhas, estados, critérios originais e todas as células fora das três colunas actualizadas. Não se converteu falta de prova em falta de implementação nem se criou novo inventário.
Primeiro trabalho executado nesta passagem: `AUD-EDU-01/03`, continuidade da revisão de procura/capacidade ao consultar histórico. A paginação normal obrigava a abandonar a preparação para navegar; os dois sentidos passam a conservar o estado e a posição, no mesmo detalhe. Chave por declaração/tipo/actor continua a separar contextos. Quatro verificações novas aprovadas: fundamento conservado, tentativa incerta repetida com a mesma chave, versão nova exige comparação e actor diferente não recebe o fundamento anterior. Tipos globais e lint dos dois ficheiros alterados aprovados. Nenhum teste anterior reexecutado; nenhuma alteração de writer, schema, permissões, dados ou histórico. Delta novo local, sem instalação remota ou prova visual presumida; não confundir com `ff726e57` já instalada.
Compilação QA do delta concluída com código 0; apenas `public/build-qa-opportunities` actualizado para a instalação local existente. Logs de tipos/build: `storage/logs/education-history-continuity-20261110-{types,build}.log`. Build servido de produção, ZIP selado, dados e cluster QA preservados. Não foi feita instalação remota deste delta.
Saldo de Educação: retoma durável da preparação de revisão, jornadas externas/representação sem cadastro aprovado e conferência visual. O delta resolve a perda de continuidade dentro da mesma página, não persistência entre sessões. Próximo trabalho na ordem acordada: continuar a funcionalidade fiscal L8 com fonte executável; preservar IVA/IRPC/PEC e exportações aceites, bloquear apenas os casos cuja regra/prova específica falta. O portal inteiro não fica concluído por este incremento.
## Entrega conjunta preparada em 10/11/2026
[Contador e aliases na mesma entrega](entrega-conjunta-contador-aliases-2026-11-10.md): base `main` e `origin/main` actualizadas e coincidentes em `2209b838`; pacote de fontes com manifesto, patch conjunto e inverso, conferidos num índice temporário. Oito verificações novas de contexto/rotas, 56 asserções; provas aceites do contador e aliases reutilizadas, sem repetir suites anteriores. Sem instalação, push, dados de negócio, configuração ou build servido alterados.
Saldo em três pontos independentes: **disponibilização conjunta pendente; revisão visual pendente; janela S08 como trabalho por concluir, sem conclusão comunicada**. A S08 não é encerrada pela compilação, pelos testes do contador, por uma query transportada ou pela futura revisão visual. Preservar as provas já aceites e os estados/critérios da matriz.
## Continuação de 10/11/2026 — contador preparado; disponibilização e saldo preservados
[Delta multiterritorial e provas](revisao-externa-2026-11-10.md): agregação SQL de IDs únicos de oportunidades públicas e províncias declaradas distintas, sem multiplicar o total pelas localizações. Uma oportunidade em duas províncias conta uma vez no total. Nova chave de cache, invalidadores existentes conservados e definição visível preparados. Nove testes novos/23 asserções passaram, além de PHPStan, Pint, tipos, lint e build em saída privada. Diagnóstico READ ONLY: seis oportunidades/cinco províncias; sem dados de negócio alterados. Cluster QA existente iniciado apenas para tabelas temporárias/rollback e reposto no estado parado; nenhuma base ou migration criada.
**Sem disponibilização no portal**, tanto do contador como dos aliases `/simulador/` e `/contactos/` já corrigidos/testados. **A revisão visual e as melhorias da janela S08 continuam no saldo, sem conclusão comunicada.** Preservar provas aceites e não confundir janela S08 com uma etapa homónima do workflow. A agregação preparada não encerra o P0 inteiro, a S08 ou requisitos territoriais transversais.
## Revisão externa inicial de 10/11/2026
[Reconciliação e primeiro delta P0](revisao-externa-2026-11-10.md): destino confirmado `portal-investidor`; inventário de 3.374 ficheiros e consulta da base configurada apenas em transacção READ ONLY. Aliases `/simulador` e `/contactos` acrescentados para os destinos existentes, com contexto conservado. Nove testes/37 asserções passaram sem base ou envio de email real. Seis IDs da matriz existente receberam somente fontes/evidências, com 3.555 linhas e todos os estados/critérios anteriores preservados.
Saldo P0: agregação multiterritorial e definição visível do contador, conferência de contactos de apoio/identidade já decidida e reprodução visual do cabeçalho em desktop/móvel. Navegador indisponível e HEAD HTTPS impedido pela verificação de revogação Schannel; router directo não substitui essas provas. Nenhuma migration, seed, concessão, parâmetro fiscal, publicação ou deploy. Não recriar simulador, Balcão, biblioteca, contas ou protocolos. Fases estruturais/fiscais seguem a reconciliação específica; nenhuma conclusão global presumida.
<!-- saldo-atual-20261018 -->
O [saldo atualizado em 18/10/2026](visao-consolidada-modulos.md) reconcilia os recibos mais recentes, preserva os fechos aceites e distingue o estado histórico do próximo recorte verificável. As previsões antigas abaixo não devem ser somadas como horas restantes.
<!-- /saldo-atual-20261018 -->
@@ -3406,3 +3819,98 @@ A pesquisa rápida já entregue permanece demonstrada; não foi substituída por
### 03/11/2026 — AUD-COUNTRY-02 e AUD-PROFILE-25: reutilização de país com pré-visualização e confirmação repetível
[ORGANIZATION-COUNTRY-REUSE-20261103] Reutilização explícita do país organizacional com consulta/pré-visualização, confirmação, recibo repetível cifrado e fonte/versões preservadas. Guarda edições e bloqueia acções concorrentes da mesma declaração enquanto o pedido não tem confirmação. Representação de leitura actual e propriedade conferidas também no replay; snapshots/submissões anteriores não são reescritos.29 Feature/364 asserções (seis novos e23 anteriores),79 frontend (dez novos e69 anteriores),PHPStan/Pint/tipos/formato/lint/build aprovados.79 tabelas QA vazias, constraint da falha removida por rollback e índice de representação intacto. Dois processos reais aguardaram o mesmo lock e receberam o mesmo resultado/v2, com uma cópia cifrada e um recibo.89 recibos/411 provas preservados;16 provas novas. Recibo organization_country_reuse_increment_20261103. Parcial local,zero escrita/migração activa.
### 04/11/2026 — conferência visual da main na QA isolada e correcção do CSV provincial
Percurso executado no Edge em `http://127.0.0.2:18644`, base exclusiva `main_visual_20261104_test` no cluster QA `127.0.0.1:55441`, com actor e conteúdos sintéticos. Autenticação Fortify normal com MFA: editar, guardar v2, recarregar e conferir os valores; aprovar v3; guardar novo rascunho v4; terminar sessão e conferir publicamente apenas v3, sem o marcador do rascunho. Histórico v1–v4 preservado. Esta prova conclui o recorte visual local de Competitividade; não equivale a execução no portal remoto ou aceitação da equipa em dados reais.
Filtro apenas por Maputo Província, com sector Agronegócio: cinco oportunidades existentes mais quatro fixtures sintéticas nos níveis província/distrito/posto/localidade resultaram em nove na grelha, lista, mapa e CSV. Distrito de Matola apresentou três; regressar a toda a província recuperou nove e limpou distrito/posto/localidade. Nenhuma coordenada criada: mapa informa zero locais e nove sem coordenadas. Hash das oportunidades anteriores idêntico antes/depois. Limite administrativo desta província indisponível na QA, explicitamente indicado; cobertura cartográfica transversal continua pendente.
Detectada e corrigida divergência no CSV: coluna Província vazia apesar da localização canónica/contexto de corredor disponível. Exportação reutiliza a apresentação territorial existente, conserva as oito primeiras colunas e o escape contra fórmulas, acrescentando Localização e âmbito. Contexto do corredor indica expressamente que distrito/posto não foram indicados; não grava nem inventa localização. CSV descarregado pela interface após correcção: nove linhas, todas com província e âmbito coerentes.
Delta aprovado: 17 testes de base de dados, 609 asserções; PHPStan sem erros e Pint aprovado. Provas anteriores de 1.325 testes/28.377 asserções preservadas, sem repetição integral. Recibo com hashes e capturas: `docs/investmoz/unification/main-local-visual-qa-20261104.json`; artefactos em `outputs/main-visual-qa-20261104/`. Apenas main local e QA; sem push, escrita activa ou alteração de permissões activas. Conferência dos filtros no portal de destino e pendências gerais ACT-02/V2-07/V2-10 continuam abertas.
### 04/11/2026 — ACT-20261029-02: limites do nível imediatamente inferior e lacunas de cobertura
Continuação sequencial na main local sobre `6dde36b5`, reutilizando V2-07/V2-10, serviço de geometria, permissões e mapa único. Inventário prévio confirmou camadas/legenda/fontes já existentes e contexto limitado à selecção/antepassados; nenhuma perda confirmada. Os 12 artefactos do recibo visual anterior foram novamente conferidos por SHA256, sem alteração. Provas aceites de 1.325 testes e demais incrementos preservadas, sem repetição integral.
O catálogo solicita `include_children=1` no endpoint administrativo existente. A opção acrescenta apenas referências do nível imediatamente inferior: distritos de uma província, postos de um distrito e localidades de um posto. Sem a opção conserva o âmbito anterior. Geometrias continuam sujeitas à classificação, acesso efectivo e snapshots/hashes de divulgação existentes; não há publicação, importação ou nova autorização. O território seleccionado tem prioridade, seguido dos antepassados e subdivisões. Até 100 geometrias e 500 referências inferiores, com truncagem explícita.
Resposta acrescenta `child_territory` e `child_coverage` com nível, referências acessíveis e territórios representados, distinguindo contagem territorial de polígonos. Interface identifica subdivisão/contexto/território seleccionado, conserva fonte/edição e explicita que falta de limite na consulta não significa inexistência do território, nem terreno disponível. Nenhuma oportunidade é reposicionada e nenhuma coordenada é presumida.
Delta aprovado: 11 testes de base de dados/125 asserções (cinco novos e seis de acesso existente), nove testes frontend, PHPStan sem erros, tipos, Pint, lint e build QA aprovados. Inclui exclusão de outra província/netos, limites públicos/internos, referência eliminada, ausência de geometria, priorização e truncagem de geometrias/referências, validação do parâmetro e descarte de resposta anterior ao mudar a opção. Primeira tentativa da fixture falhou por faltar país-pai; corrigida a fixture, mantendo a integridade territorial e os logs iniciais.
Conferência visual no Edge, exclusivamente QA `127.0.0.2:18644`/`main_visual_20261104_test`: Maputo Província mostra zero limites de oito referências distritais acessíveis, lacuna explicitada, nove oportunidades mantidas sem ponto inventado. O snapshot não contém limites públicos para demonstrar visualmente os polígonos inferiores; estes foram exercitados por testes sintéticos. Recibo/hashes: `docs/investmoz/unification/map-subdivision-local-20261104.json`; captura em `outputs/map-subdivision-20261104/`.
**ACT-02 permanece parcial.** Camadas factuais de recursos, infra-estruturas, mão-de-obra e potencial económico, cobertura de dados/geometrias e conferência no portal de destino continuam pendentes. Este recorte não encerra V2-07/V2-10 nem substitui aceitação da equipa. Sem push, migração, escrita activa, importação, alteração de permissões ou reinício de serviços.
### 04/11/2026 — ACT-20261029-02: camadas de evidência pública de recursos e infra-estruturas
Incremento sequencial na main local sobre `bbc05f1d`, reutilizando V2-07/V2-10, mapa único, filtros, pontos/áreas públicos e contexto administrativo já demonstrados. Inventário de código e cópia QA antes de fixtures: 48 oportunidades publicadas e duas sínteses proactivas públicas, mas nenhuma evidência pública local das categorias resource/infrastructure. Os campos existentes de zonas e fontes editoriais não foram convertidos automaticamente em disponibilidade ou geometrias temáticas; evidências privadas não foram publicadas. Não é perda confirmada de dados nem diagnóstico do remoto. Conferidos por SHA256 os 23 artefactos dos dois recibos visuais/administrativos anteriores, todos inalterados.
A API existente de mapa acrescenta `thematic_records` a partir de `ProactiveInvestment::publicData`: somente evidência seleccionada na síntese publicada, categorias recursos/infra-estruturas e propósito local. Conserva facto, fonte, localizador, período, âmbito, cobertura, verificação registada, situação e limitações. Exclui metodologia e outras categorias; não copia o rascunho privado. Mantém a consulta territorial, limite/truncagem e autorização pública anteriores. As áreas acrescentam o identificador da oportunidade para filtragem, sem atribuir a geometria à evidência.
No mesmo mapa, o selector permite todas as oportunidades, recursos documentados ou infra-estruturas documentadas. A camada filtra apenas pontos/áreas existentes das fichas correspondentes; não alarga a pesquisa geral, não altera CSV nem cria posições. Fichas sem ponto continuam consultáveis no painel de evidências/fontes. Contagens referem fichas, não quantidade/capacidade de recursos. Âmbito da evidência distingue-se da localização da oportunidade; marcadores não representam posição exacta ou disponibilidade de recurso/infra-estrutura. Escolha recuperada na sessão do navegador, com tolerância a armazenamento indisponível/inválido. Falta de evidência, período ou fonte é explícita, sem zero presumido.
Delta aprovado: oito testes de BD/438 asserções (quatro novos e quatro de filtros provinciais), 14 testes frontend (cinco novos e nove existentes), PHPStan sem erros, tipos, Pint, lint e build QA aprovados. Inclui exclusão de rascunhos/publicação futura/acesso restrito, leitura apenas da síntese pública, fonte/período/limites, província/distrito, reutilização das coordenadas existentes, alternância/reposição de camadas, fichas sem ponto, carregamento/ausência/truncagem, escape de texto e recuperação da escolha. Provas gerais anteriores preservadas, sem repetir a bateria completa.
Conferência visual no Edge exclusivamente em `127.0.0.2:18644`, base `main_visual_20261104_test`: duas fixtures claramente sintéticas criadas apenas em QA, uma por categoria; nenhuma coordenada/geometria criada. Hash de todas as oportunidades anteriores idêntico antes/depois. Alternância entre ambas conserva pesquisa provincial e duas fichas gerais; cada camada mostra a sua ficha, fonte/período/âmbito/limites e ausência de ponto. Infra-estruturas recuperada após recarregar. Recibo: `docs/investmoz/unification/thematic-map-local-20261104.json`; inventário, hashes, DOM e capturas em `outputs/thematic-map-20261104/`. Demonstração sintética não constitui cobertura económica factual ou publicação de dados reais.
**ACT-02 permanece parcial.** Cobertura factual de recursos/infra-estruturas no destino, georreferenciação própria quando documentada, mão-de-obra, potencial económico quantitativo, restantes fontes temáticas e lacunas cartográficas continuam pendentes. Não se inferem estas camadas de descrições nem do número de oportunidades. Mantêm-se conferência remota e aceitação da equipa; V2-07/V2-10 não encerrados. Sem push, alteração activa, migração, publicação automática ou alteração de permissões/serviços.
### 04/11/2026 — ACT-20261029-02: mão-de-obra documentada e contexto económico
Incremento qualitativo na main local sobre `21ebad73`, reutilizando mapa, evidências, síntese publicada, divulgação expressa e fontes/períodos existentes. Inventário na cópia QA: duas evidências públicas locais de actividade anteriores (fichas sintéticas 350/383), nenhuma de mão-de-obra/oferta/procura; recursos/infra-estruturas da demonstração anterior conservados. As declarações labour_demands/training_capacities têm acesso EducationAccess interno; emprego/indicadores financeiros canónicos não fazem parte da publicProjection. Observações de zonas conservam divulgação/unidades/metodologia próprias. Não foram expostas nem agregadas essas fontes privadas por existir revisão interna. As fontes oficiais aceites e as decisões de divulgação anteriores não foram reabertas.
Acrescentada categoria explícita Mão-de-obra no editor e validação da evidência existente, sem migração ou reclassificação automática dos textos anteriores. Guardar, recuperar, pré-visualizar e confirmar publicação usam o percurso existente. A camada económica reutiliza apenas evidências públicas locais de actividade/oferta/procura, mantendo `source_category` original, facto, fonte, localizador, período, verificação, âmbito, cobertura e limitações. Agrupá-las para consulta é uma escolha de apresentação, não aprovação de viabilidade nem índice de potencial.
Ambas as opções entram no selector do mapa único; a escolha é recuperada na sessão. Recursos/infra-estruturas, geometrias públicas, filtros territoriais, fichas sem ponto e contagens anteriores mantêm-se. Nenhuma posição é criada ou atribuída ao recurso/mão-de-obra. Necessidade ou qualificação não equivale a trabalhador disponível; previsão de emprego não é emprego efectivo. Contexto económico não calcula retorno, score ou viabilidade. Períodos/fontes incompatíveis permanecem separados, sem totais ou zero presumido.
Delta aprovado: sete testes de BD/77 asserções (cinco do mapa, incluindo o novo caso; dois de publicação, um novo e um de acesso/fonte privada já existente), 22 testes frontend, PHPStan sem erros, tipos, Pint, lint e build QA aprovados. Teste do percurso: criar evidência workforce → recuperar → confirmar pela publicação existente → consultar mapa → alterar rascunho privado sem substituir a categoria/fonte públicas. Testes mantêm metodologia/internos fora do mapa, categorias originais, períodos distintos e ausência de quantidades/índices inferidos. Não se repetiu a bateria geral aceite.
Conferência visual no Edge exclusivamente na QA `127.0.0.2:18644`/`main_visual_20261104_test`: contexto económico reutiliza a evidência de actividade já publicada da ficha 383, sintética e explicitamente identificada. Mão-de-obra usa uma única fixture nova, também sintética; escolha e fonte recuperadas após recarregar. Nenhuma coordenada/geometria criada; hash de todas as oportunidades anteriores idêntico antes/depois. Conferidos 36 artefactos dos três recibos anteriores, todos com SHA256 inalterado. Recibo: `docs/investmoz/unification/workforce-economic-map-local-20261104.json`; inventário, DOM, capturas e hashes em `outputs/workforce-economic-map-20261104/`.
**ACT-02 permanece parcial.** Cobertura factual laboral/económica, séries, unidades e metodologia quantitativa comparável, georreferenciação própria documentada e cobertura cartográfica continuam pendentes. A demonstração sintética não mede oferta laboral nem potencial regional real. Mantêm-se conferência remota e aceitação da equipa; V2-07/V2-10 não encerrados. Sem push, migração, publicação automática, escrita activa, alteração de permissões ou reinício de serviços.
### 04/11/2026 — ACT-20261029-02: observações quantitativas públicas de emprego e contexto económico
Incremento na main local sobre `a9241ff2`. Inventário prévio: fichas de parques já conservam observações com valor, unidade, período, âmbito, população/base, metodologia, conhecimento e divulgação seleccionada. Reutilizada apenas a versão pública de `operational_details.public.integral.observations`, sem desencriptar preparação privada. Na cópia QA havia oito fichas públicas e zero observações quantitativas divulgadas; não foi declarada perda nem ausência no remoto. As 51 provas anteriores foram conferidas por SHA256 antes da ronda e novamente no fim, todas inalteradas; bateria geral aceite preservada sem repetição integral.
A API de mapa acrescenta `quantitative_context` a partir do mesmo conjunto de zonas já devolvido no contexto territorial: emprego para mão-de-obra; investimento, produção, exportações e compras locais para contexto económico. Reutiliza os filtros/autorização existentes, sem alargar a pesquisa de oportunidades nem transformar indicadores do parque em indicadores das oportunidades. Filtros específicos que já excluem esse conjunto devolvem âmbito não incluído, em vez de uma falsa medição vazia. Até 30 fichas e 200 observações; truncagem explícita. Geometrias, divulgação, fontes e versões anteriores conservadas.
O painel do mapa mostra cada valor com a sua unidade, período, âmbito, população/base, metodologia, fonte/data, versão pública e ligação à ficha. Zero documentado e valor desconhecido são distintos; precisão decimal conserva-se como texto. Divergências e metadados em falta permanecem visíveis. Emprego não é oferta de trabalhadores disponíveis; natureza prevista/efectiva depende da descrição da fonte. Não se somam populações ou períodos, não se convertem unidades/moedas e não se criam médias, índices ou previsões de retorno. Contagens de oportunidades e de observações territoriais têm âmbitos distintos. Erro, carregamento, cadastro indisponível, âmbito não consultado e ausência de observações são estados diferentes.
Verificação dirigida aprovada: 11 testes BD/99 asserções (seis novos e cinco do mapa temático existente), 18 testes frontend (três novos e 15 existentes), PHPStan sem erros, tipos, Pint, lint e compilação QA aprovados. Inclui zero/decimais, desconhecido/divergência, metadados incompletos, exclusão de preparação privada/ficha não publicada, preservação do envelope, província/distrito, limites de observações/fichas e âmbito excluído. Primeira tentativa teve uma expectativa inadequada sobre a serialização JSON antes da normalização jsonb; comparação corrigida para duas leituras efectivas do servidor. Fixture frontend com rótulo repetido corrigida; não se relaxaram regras do produto. Logs BD iniciais mantidos.
Conferência visual exclusivamente em `127.0.0.2:18644`/`main_visual_20261104_test`: uma ficha sintética nova com cinco observações. Camada laboral mostra zero previsto e emprego indirecto por determinar, em duas linhas; económica mostra 125.7500 toneladas, 100000.00 MZN e 25.50 USD em divergência, em três linhas, com as respectivas fontes e períodos. Valores exclusivamente de teste, sem aplicação económica real. Nenhuma coordenada/geometria criada; hash de todas as fichas territoriais anteriores idêntico antes/depois. Recibo e hashes: `docs/investmoz/unification/quantitative-map-local-20261104.json`; inventário, capturas e DOM em `outputs/quantitative-map-20261104/`.
**ACT-02 permanece parcial.** Cobertura factual nacional/regional, séries comparáveis, oferta laboral, natureza das grandezas e metodologia quantitativa de potencial económico continuam pendentes conforme dados aplicáveis. A demonstração sintética não é estatística real, índice regional ou diagnóstico do portal remoto. Mantêm-se conferência remota e aceitação da equipa; V2-07/V2-10 não encerrados. Sem push, instalação, escrita activa, migração, publicação automática ou alteração de permissões/serviços.
Precisão adicional demonstrada: número JSON `1234567890123.456789` recuperado sem arredondamento. O valor é extraído como texto decimal do mesmo snapshot público carregado, sem o converter num float PHP nem misturar metadados e versão com uma leitura posterior. O teste dirigido de precisão e PHPStan passaram após esta correcção.
### 04/11/2026 — ACT-20261029-02: filtros de indicador e período das observações quantitativas
Main local sobre `3f50f666`, sem push. Inventário do concluído: mantidas as camadas temáticas, hierarquia territorial, observações públicas, precisão decimal, fonte/data/versão, estados de conhecimento e limites de cobertura já demonstrados. Conferidos por SHA256 os 63 recibos JSON anteriores da pasta de unificação, todos inalterados. Não foi identificada perda neste delta; as provas aceites, incluindo 1.325 testes/28.377 asserções, não foram reabertas. A instalação anterior de `3f50f666` mantém recibo em `outputs/main-install-20261104/installation-result.json`; os estados «sem instalação» das entradas anteriores descrevem a sua execução histórica.
Próximo recorte dos filtros previstos na matriz: selecção por indicador e período de referência sobre as observações quantitativas públicas já recebidas no mapa. Sobreposição temporal inclusiva, com início e/ou fim opcionais; intervalo invertido apresenta erro. Períodos incompletos, inválidos ou invertidos da fonte continuam consultáveis sem filtro temporal e são excluídos quando este é aplicado; a data da fonte não substitui o período. A limpeza repõe as observações. Não altera a pesquisa de oportunidades, os limites de resposta ou a cobertura territorial; não soma, converte, estima nem infere comparabilidade. Escolha de implementação delimitada aos filtros da ACT-02, sem nova regra organizacional.
Delta verificado: 12 testes frontend dirigidos aprovados, tipos globais, lint do componente e build QA aprovados. Conferência visual apenas em `127.0.0.2:18644`/`main_visual_20261104_test`, reutilizando as cinco observações sintéticas anteriores, sem novas fixtures ou escrita de dados. Produção em 31/12/2025 devolve uma linha com `125.7500` toneladas; intervalo invertido mostra erro; limpar repõe cinco linhas. As 12 oportunidades do cenário QA mantêm-se. Corrigida e conferida a actualização dos campos de data durante a introdução. Recibo e hashes: `docs/investmoz/unification/quantitative-map-filters-local-20261104.json`; DOM, captura e logs em `outputs/quantitative-filters-20261104/`.
**ACT-02 permanece parcial.** Cobertura factual nacional/regional, séries comparáveis, oferta laboral e metodologia quantitativa de potencial económico continuam pendentes conforme fontes aplicáveis. O cenário sintético não é estatística real. Conferência autenticada de Competitividade pela equipa e requisitos amplos V2-07/V2-10 preservados. Sem push, instalação deste delta, migração, importação, publicação automática, escrita activa, alteração de permissões ou reinício de serviços.
### 10/11/2026 ? commits separados; S08 e visual pendentes
Ajustes locais separados por ?mbito na branch `entrega/directiva-20261110`: pacote contador/aliases `377c40a2`; contactos `4e614802`; informa??o/SEO `4e07557c`; pr?-dossier `dcecd251`; curadoria `7f131497`; sitemap `252f38bb`. Cobertura, limites e hashes em `docs/investmoz/v2/auditoria-portal/ajustes-por-ambito-2026-11-10.md`. Provas aceites preservadas. Sem push ou instala??o.
S08 permanece por concluir, separada da revis?o visual; a especifica??o da janela n?o foi recuperada e n?o ? substitu?da pela etapa de licenciamento hom?nima. Visual interrompido por impossibilidade da ferramenta de confirmar o URL, n?o por Escape nem por falha demonstrada do portal. Cluster QA 55441 mantido ligado enquanto houver depend?ncias por conferir. Nenhum requisito amplo ? promovido a conclu?do por estes commits.
### 10/11/2026 ? S08: melhoria do percurso existente de licenciamento pr?vio
?mbito esclarecido pela Tempo; substitui o anterior saldo ?por identificar?. Implementa??o e sete provas frontend dirigidas em `67344418`: estado sem repeti??o, registo/ac??o principal vis?veis, redu??o dos blocos expans?veis e abertura/foco da etapa por `#S08`, preservando campos/contexto/vers?o/grava??o e guardas existentes. TypeScript, lint dirigido e build QA aprovados. Sem altera??o de backend/hist?rico/dados ou repeti??o das provas aceites n?o afectadas. Detalhes e limites em `docs/investmoz/v2/auditoria-portal/ajustes-por-ambito-2026-11-10.md`. Revis?o visual e instala??o mant?m-se pendentes e separadas. Pacote contador/aliases preservado; sem push e cluster 55441 mantido ligado.
@@ -0,0 +1,43 @@
# Diagnóstico da ligação ao navegador e preparação da entrega
O portal não foi instalado. S08 `67344418`, o registo `659c039b` e o pacote selado de contador/aliases permanecem intactos. A revisão visual continua por executar. Este diagnóstico não atribui uma falha ao portal nem substitui testes visuais por testes de componentes.
## Ligação ao navegador
Diagnósticos executados com os scripts fornecidos pelo plugin Chrome, versão local `26.1103.32239`:
| Componente | Resultado observado |
| --- | --- |
| `chrome-is-running.js --browser chrome --check` | Chrome em execução; exit 0 |
| `installed-browsers.js --json` | Chrome e Edge instalados; navegador predefinido encaminhado pelo Parallels |
| `check-extension-installed.js --browser chrome --json` | Extensão `hehggadaopoacecdllhhajmbjkdcmajg`, versão `1.26.1102.11740_0`, instalada e habilitada no perfil Default; exit 0 |
| `check-native-host-manifest.js --browser chrome --json` | Manifesto, nome, origens permitidas e registo HKCU correctos; exit 0 |
| Processo nativo | `extension-host.exe`, PID 19492, presente; não foi terminado |
| Edge | Extensão habilitada em Profile 2; manifesto correcto. Usa o mesmo host nativo. Não foram alterados perfis ou permissões |
| Alternativa suportada | `mcp__cua_repl.js`: `cua.createBrowserTab('iab','about:blank',{visible:true})` devolveu exactamente `The browser access host is unavailable` |
Na tentativa anterior, o inventário nativo identificou a nova janela Chrome `658216`, título “Tempo - Google Chrome”, mas o inventário de browsers devolveu `Browsers: Error: The browser access host is unavailable`. `cua.getApp({windowId:658216})` encerrou o controlo com `could not determine the current browser URL on Windows with enough confidence to enforce policy`. Nesta ronda foi testado o navegador integrado pelo mecanismo documentado, sem repetir a abertura do Chrome ou provocar novamente o encerramento na confirmação de URL.
**Camada da falha delimitada:** o serviço de acesso ao navegador exposto à sessão está indisponível tanto para descoberta Chrome como para criação IAB, antes de qualquer navegação. A instalação da extensão e o manifesto passam nos diagnósticos. Isso exclui tratá-los como comprovadamente ausentes, mas não demonstra uma comunicação funcional fim a fim. A causa interna da indisponibilidade desse serviço não ficou estabelecida.
Conferidos registos locais do desktop e SQLite de logs, apenas para diagnóstico, sem copiar credenciais ou histórico de navegação para a entrega. Há eventos anteriores `browser_use_iab_backend_startup_ready` e pipes `codex-browser-use-*` existentes. Não foi encontrado um erro específico que explique a indisponibilidade actual. O catálogo local `chrome-native-hosts-v2.json` conserva entradas antigas: a entrada da versão actual indica PID 5564, já inexistente, com última actualização em 07/11; isto é uma anomalia de registo observada, **não uma causa raiz demonstrada**. Não se editaram esses registos nem se ligou manualmente aos pipes.
O transporte nativo configurado é stdio/pipes; não foi identificada uma porta TCP do host em conflito. Conferidas as ligações locais: 55441 pertence ao PostgreSQL PID 20756; 8000 ao servidor PHP PID 14316; 5173 ao Vite PID 17180. Sem ocupação observada em 5180, 9222 ou 9223. Não se activou depuração remota, não se libertaram portas e nenhum desses serviços foi parado. A ausência de um conflito observado não é prova de toda a configuração de rede.
**Intervenção específica para recuperar:** guardar o trabalho e encerrar/reabrir a aplicação desktop ChatGPT/Codex, para recriar o serviço de acesso e a ligação desta sessão; reiniciar só Chrome já foi tentado sem resolver. Essa operação termina a sessão de controlo e deve ser feita pelo utilizador. Depois, a primeira verificação é `cua.listBrowsers()` e uma abertura IAB, antes de tocar em S08. Se o serviço continuar indisponível, reinstalar o plugin Browser/Chrome pela interface oficial de Plugins e repetir o diagnóstico, ou fornecer estes erros ao suporte da aplicação. Não reinstalar o portal ou desligar QA.
O guia local [Chrome troubleshooting](C:/Users/macbook/.codex/plugins/cache/openai-bundled/chrome/latest/docs/chrome-troubleshooting.md) determina: “Do not install or repair the native host yourself.” Não foi executada reparação manual do manifesto/host. A [documentação oficial de configuração](https://developers.openai.com/training/walkthroughs/using-your-computer-and-browser) descreve a instalação dos plugins Browser e Chrome e da extensão; não estabelece a causa interna deste erro específico.
## Revisão do código e compatibilidade
`git fetch --no-tags origin main` executado com sucesso. `main` e `origin/main` continuam em `2209b8382019b0bbc2be57b3f6e665a3c94dd032`, a base preparada. Nenhum push ou alteração de main.
Revistos os deltas de S08, formulário/endpoint/contexto e condicionais de autoridade; curadoria com autorização nativa/MFA, prévia vinculada ao actor/versão/intent, transacção e recuperação; sitemap limitado ao scope público/origem configurada; pré-dossier sobre a projecção autorizada comum. Esta revisão não é nova homologação integral nem ensaio de carga.
Identificado um caso adicional em S08: uma resposta de gravação que muda `path.next.code` pode fechar a fase controlada, apesar de o fragmento continuar em `#S08`. Correcção adicional em `03bcaf38`: o efeito de abertura também acompanha essa mudança, sem alterar campos ou contrato de gravação. Commit original preservado. Executado apenas o teste afectado com `node --test --test-name-pattern='server next-stage updates' tests/frontend/licensing-stage.test.mjs`: 1/1 aprovado; TypeScript global aprovado. As outras seis provas aceites e as suites do servidor não foram repetidas.
## Entrega preparada e saldo
Entrega incremental de fontes em `outputs/entrega-revisada-20261110/`, com manifesto, patches cumulativos/incrementais, série de commits e reversões. A conferência em índices Git temporários valida aplicação e reversão contra a base de main e contra a árvore selada do contador/aliases, sem tocar no índice de trabalho ou instalar ficheiros. Os hashes finais constam do manifesto e do recibo de compatibilidade do pacote. Builds QA anteriores permanecem provas locais, não artefactos para produção; o delta adicional tem prova dirigida e TypeScript, sem novo build instalado.
Continuam separados: revisão visual real de S08 e dos percursos afectados; instalação/activação; confirmação operacional autenticada no destino. O pacote não autoriza publicação automática nem concessões ou operações de negócio. Cluster QA 55441 ligado; servidor e workers existentes preservados.
@@ -0,0 +1,50 @@
# Entrega conjunta do contador e dos aliases
## Resultado
Pacote de fontes preparado em 10/11/2026 para reunir o contador multiterritorial e os aliases `/simulador/` e `/contactos/`. Sem instalação, publicação, push ou alteração de dados/configurações. O pacote contém patch conjunto, patch inverso, fontes seleccionadas, manifesto de hashes, recibos e procedimento de aplicação/reversão futura.
Directório local: `outputs/entrega-conjunta-contador-aliases-20261110/`; arquivo com o mesmo nome e extensão `.zip`. O manifesto e `compatibilidade.json` identificam exactamente a base, a árvore candidata e os resultados da aplicação/reversão. É uma entrega de fontes; o build QA anterior é evidência e não um build de produção a instalar.
## Compatibilidade com main
A referência remota foi actualizada por `git fetch --no-tags origin main`. `main`, `origin/main` e `FETCH_HEAD` coincidem em `2209b8382019b0bbc2be57b3f6e665a3c94dd032`. A preparação não altera branches nem cria um commit em `main`.
O patch é verificado num índice Git temporário iniciado nessa base: `git apply --cached --check`, aplicação ao índice e comparação da árvore resultante com a candidata. O patch inverso é conferido e aplicado no mesmo índice; a árvore final deve ser exactamente a árvore da base. O índice de trabalho original é preservado por hash. Nenhuma destas operações instala os ficheiros na árvore de trabalho ou no portal.
Esta prova cobre a base exacta identificada. Se `main` avançar, é necessário voltar a conferir o delta afectado; não se declara compatibilidade com commits futuros. A lista de ficheiros é fechada e não inclui `.env`, credenciais, dumps, dependências, builds servidos ou alterações do SGF Tempo.
## Ajustes incluídos
- Total de oportunidades por ID público único; várias localizações não multiplicam a oportunidade. Províncias declaradas distintas a partir da referência principal e das adicionais, pelas regras públicas existentes.
- Chave de cache versionada, invalidação nos dois idiomas e definição do indicador junto da contagem. Sem limpeza da cache activa ou alteração da validade de dez minutos.
- Aliases minúsculos, com e sem barra final, para as rotas existentes `/simulador-fiscal` e `/apoio`, por 301. O alias histórico `/Simulador` e os destinos oficiais são preservados.
- Testes e registos necessários à entrega conjunta, incluindo a matriz e o manual existentes. Sem migrations, novas tabelas de negócio, direitos ou parâmetros fiscais.
## Contexto nos redireccionamentos
Oito casos novos passaram, com 56 asserções: GET e HEAD nos dois aliases, com e sem barra final. Os testes conferem 301, esquema e host, caminho canónico, nome da rota existente e equivalência dos parâmetros após codificação/descodificação.
O contexto inclui dossier, oportunidade, idioma, origem/território aninhados, retorno com query própria e texto com acentos e caracteres reservados. Um parâmetro `redirect` com URL externa permanece dado de consulta; não substitui o destino. O valor de etapa transportado dentro de um retorno é opaco: conservá-lo não demonstra a funcionalidade dessa etapa ou da janela S08.
Os testes executam a colecção de rotas e a resposta, com acesso à base proibido. Não são prova de navegador, middleware completo, nova autenticação, retomada de dossier ou apresentação visual do destino. Fragmentos do browser não são parâmetros enviados ao servidor e não foram ensaiados.
## Provas anteriores reutilizadas
O recibo `home-territorial-delivery.json` continua a corresponder, por SHA-256, aos sete ficheiros de fontes/testes do contador e ao manifesto do build privado anterior. Reutilizam-se os nove testes/23 asserções e as verificações de tipos, lint, PHPStan, Pint e compilação nesse âmbito. Não se repetiram a suite SQL, os testes aceites dos aliases, os ciclos do simulador/Balcão ou provas da S08. O teste novo passou Pint; `git diff --check` não detectou erros de whitespace no delta.
As provas históricas e os limites da leitura READ ONLY seis oportunidades/cinco províncias estão no [relatório anterior](revisao-externa-2026-11-10.md). Não há novo ensaio de dados de negócio, arranque de QA ou consulta à base nesta preparação.
## Saldo preservado
1. **Instalação/disponibilização conjunta:** pendente. O pacote preparado não torna contador ou aliases disponíveis no portal.
2. **Revisão visual:** pendente, com âmbito próprio de desktop/móvel e percursos afectados. Aplicação limpa do patch, testes de router e build não a encerram.
3. **Janela S08:** trabalho por concluir, sem conclusão comunicada. É uma pendência separada da revisão visual e não foi resolvida pelos testes do contador ou pelo transporte de contexto. As provas já aceites mantêm-se; não se atribui conteúdo à janela a partir de uma etapa homónima.
## Procedimento de entrega e reversão
Antes de uma futura aplicação, conferir a base e os hashes do manifesto, preservar alterações locais e efectuar a verificação do patch no destino correcto. A instalação exige o procedimento já aplicável ao portal e a autorização correspondente; não executar estes passos sobre dados activos por inferência desta preparação.
O pacote fornece um delta único para evitar instalar o contador sem os invalidadores/definição, ou distribuir os aliases sem o seu contexto e testes. O frontend exige o build do modo e ambiente de destino: não copiar o build QA com identificação de QA para produção. Não há migration ou comando SQL de instalação.
A reversão é delimitada aos ficheiros desse delta sobre a árvore candidata exacta. Conferir o patch inverso antes de o usar; não aplicar sobre edições posteriores nem repor o repositório inteiro. Nenhum rollback de base, limpeza de cache global, eliminação de dados, reset destrutivo ou reinstalação de dependências faz parte desta entrega.
File diff suppressed because one or more lines are too long
@@ -0,0 +1,149 @@
# Revisão das melhorias externas do InvesteMoz
## Continuação de 10/11/2026 — contador multiterritorial
**Estado: código preparado e testado; sem disponibilização no portal.** Os aliases `/simulador/` e `/contactos/` conservam a mesma situação de entrega anterior. A revisão visual e as melhorias da **janela S08**, cuja conclusão não foi comunicada, mantêm-se no saldo. Esta referência à janela S08 não lhe atribui por inferência o conteúdo da etapa de licenciamento com o mesmo identificador. Provas e aceitações anteriores permanecem preservadas.
### Comportamento corrigido
O total usa `COUNT(DISTINCT id)` sobre a selecção pública original de `Opportunity::published()`. Uma oportunidade com dois distritos na mesma província e outro território numa segunda província acrescenta **uma oportunidade e duas províncias**. Oportunidades sem localização continuam no total. A publicação, visibilidade, data, eliminação lógica e exclusão de exemplos são revalidadas pelo scope existente.
As províncias são resolvidas a partir da referência principal e de `opportunity_locations`, seguindo a hierarquia pública existente. Ligações repetidas e nomes provinciais equivalentes após normalização não aumentam a contagem; Maputo Cidade e Maputo Província permanecem distintos. Antepassados privados, removidos, indisponíveis, ciclos ou cadeias incompletas não geram cobertura. O nome provincial legado só serve como alternativa quando não existem referências canónicas e corresponde a uma província do catálogo público. Desconhecido não se converte num território inventado.
A definição é **províncias com localização declarada nas oportunidades públicas**, apresentada junto aos indicadores. Abrangência nacional, impacto e contexto de corredor não são automaticamente convertidos em implantação. É uma definição de apresentação deste indicador, não uma alteração de localizações, relações, filtros ou decisões organizacionais.
A agregação usa uma única instrução SQL com CTEs e mantém separados o universo de oportunidades e a cobertura territorial. Não há carregamento do universo em PHP nem consultas por oportunidade. A inspecção de schema do scope existente é preservada: observaram-se duas consultas para esta projecção, sem incluir o resto da homepage, middleware ou bootstrap. Isto não é prova de carga sustentada.
A cache da homepage passa a `portal_home_territorial_v2_{locale}`, conservando o prazo de dez minutos e os invalidadores de gravação/eliminação de sectores e oportunidades. A chave nova evita apresentar a contagem antiga após a futura disponibilização. As chaves históricas não foram apagadas no ambiente activo. A frescura dos restantes consumidores e os eventos de escrita existentes não foram redesenhados.
### Ficheiros deste delta
- `app/Services/InvestmentGeography.php`: agregação territorial reutilizando publicação e referências públicas existentes.
- `app/Http/Controllers/Api/V1/HomeController.php`: consumo das duas contagens e chave de cache versionada.
- `app/Providers/AppServiceProvider.php`: invalidação das duas variantes linguísticas da nova cache.
- `resources/js/components/portal-hero.tsx`: definição legível do indicador e da contagem única.
- `tests/Feature/HomeTerritorialStatisticsTest.php` e `tests/Unit/HomeTerritorialCacheTest.php`: regressões dirigidas novas.
- `tests/Feature/PublicPortalNavigationTest.php`: somente referências à chave de cache actualizadas; o ciclo histórico completo não foi repetido.
- Matriz, backlog e manual existentes: referências e saldo actualizados, sem substituir critérios ou estados aceites.
### Provas e preservação
Passaram **nove testes novos, com 23 asserções**: sete casos SQL com tabelas temporárias e dois casos de cache sem base de dados. Cobrem múltiplos territórios, ligações repetidas, publicação e visibilidade restrita, ausência de localização, âmbito nacional, contexto sem implantação, fallback legado, ancestrais privados/removidos/incompletos/cíclicos, Maputo Cidade/Província, catálogo vazio, 120 oportunidades, versão de cache e invalidação nos dois idiomas. O caso com 120 oportunidades mantém o mesmo número de consultas que o catálogo vazio.
O cluster QA existente `main-qa-20261104-pgdata:55441` estava parado: a primeira tentativa dos sete testes foi recusada por ausência de ligação, antes de criar fixtures ou efectuar asserções. Foi iniciado o mesmo cluster, sem nova base, porta, role ou migration. Os testes só admitem identidade QA efectiva, criam tabelas temporárias na ligação e terminam com rollback. Depois de confirmar ausência de outros clientes, o cluster foi encerrado normalmente, repondo o estado inicial parado. Não foi usado `RefreshDatabase`, nem reconstruído o schema ou reutilizada uma demonstração como fixture descartável.
Também passaram PHPStan nos dois consumidores PHP alterados, Pint nos ficheiros PHP do delta, TypeScript global e formatação/lint do componente. O build QA concluiu em saída privada `storage/app/private/external-portal-review-20261110/home-territorial-build`, sem trocar o build servido ou executar Wayfinder. O build emitiu avisos sobre fontes opcionais e chunks grandes; não se instalaram dependências nem se declarou resolvida a carga do portal.
Uma consulta de diagnóstico, **sem cache e em transacção READ ONLY**, observou seis oportunidades e cinco províncias no destino `investemoz:5432`: duas consultas, 134,07 ms nesta execução, rollback final e zero comandos de negócio. O recibo privado é `home-territorial-readonly.json`. Não é um ensaio de escrita nem transforma essa observação isolada numa garantia de desempenho.
Não foram repetidos os testes aceites dos aliases, do simulador, do Balcão ou da S08. Não houve migration, concessão, escrita em dados de negócio, operação fiscal, publicação ou deploy. A compilação e os testes não substituem a revisão visual pendente.
### Saldo e reversão deste delta
O contador multiterritorial fica preparado/testado; a disponibilização no portal continua pendente, juntamente com os aliases anteriores. **Revisão visual e melhorias da janela S08 continuam pendentes**, preservando as provas já aceites. Não se declara encerrado o P0 inteiro ou a S08.
Reverter apenas este delta: repor o contador e a chave anterior em `HomeController`, remover `opportunityStatistics()` e o import associado, retirar as duas novas invalidações e a explicação no hero, e repor as referências do teste histórico. Não repor ficheiros completos sobre alterações posteriores, não remover os aliases entregues anteriormente e não executar rollback de migrations. Não há dados a desfazer. A compilação privada não está ligada ao portal.
## Relatório inicial de 10/11/2026
## Resultado e âmbito
Em 10/11/2026, a directiva fornecida pela Tempo foi confrontada com o projecto `C:\laragon\www\portal-investidor`, destino confirmado pelo utilizador nesta conversa. Foram corrigidos dois aliases de navegação ausentes. A leitura do ambiente demonstra que os números do relatório externo não descrevem o estado actualmente configurado. A revisão visual, o contador multiterritorial e as fases funcionais seguintes continuam abertos; esta entrega não declara concluído o programa de melhorias.
O texto colado é o pedido de execução. O DOCX é uma avaliação externa: exemplos de SQL, Node/Express, React, seeds, contactos, benefícios, câmbio e integrações nele contidos não constituem comandos ou decisões aprovadas. Não foi executado código do documento.
## Fontes e preservação
- Directiva de Implementação Segura, versão 1.0, 08/11/2026, recebida como texto colado.
- `correecoes ao portal.docx`, no caminho indicado pelo utilizador. SHA-256 `db630ab937cc00686480aa02ef0156a8ac20edadf97306995277a5173d890f51`; 5.968 elementos `w:p` extraídos. Os números de parágrafo abaixo são posições XML, não páginas verificadas por renderização. A indicação de 141 páginas pertence ao pedido.
- `AGENTS.md` do InvesteMoz, backlog mestre, matriz existente e manual de rastreabilidade. As fontes oficiais já aceites pela Tempo conservam essa aceitação; não foi criada uma obrigação genérica de nova validação de autenticidade.
- Commit inicial `2209b8382019b0bbc2be57b3f6e665a3c94dd032`. O repositório do portal não apresentava alterações Git antes desta tarefa. O SGF Tempo e o seu trabalho não integrado não foram modificados.
O inventário privado contém caminhos, tamanhos e hashes de 3.374 ficheiros em `app`, `routes`, `resources`, `database`, `tests`, `config`, `bootstrap`, `deploy`, `tools` e `docs`. São 412 ficheiros em `app`, 789 em `resources`, 140 em `database`, 675 em `tests`, 648 em `tools` e 609 em `docs`, além dos restantes directórios. A leitura automatizada de declarações e hashes dá cobertura do inventário; não constitui prova funcional de todos esses ficheiros. Dependências instaladas e artefactos de execução não foram substituídos.
As evidências locais estão em `storage/app/private/external-portal-review-20261110/`: `inventory.json`, extração integral do DOCX, `runtime-readonly.json` e cópia do ficheiro de rotas anterior. A pasta é privada/ignorada pelo Git e não é um pacote de publicação.
## Arquitectura e ambientes encontrados
A aplicação existente usa Laravel, PHP, Inertia, React/TypeScript, Fortify e PostgreSQL/PostGIS. `composer.json`, `package.json` e os respectivos locks permanecem intactos. React já integra a arquitectura; a presença de React no documento não exige nem justifica reconstruir a interface. Não foi instalado um backend Node/Express.
As rotas incluem catálogo público, sectores, oportunidades, carteira, clusters/corredores, repositório, simulador, Balcão, perfis, gestão de conteúdos e outros módulos existentes. O inventário identifica 502 declarações literais de rotas; ciclos e composição dinâmica impedem interpretar esse número como total de endpoints efectivos.
O README de execução local descreve `investmoz_local_test:55441`. A configuração realmente carregada nesta sessão aponta para `investemoz:5432`, ambiente Laravel `local`, URL `https://portal-investidor.test` e debug activo. Assim, a base configurada foi tratada como protegida. Estes dados não demonstram a configuração do domínio público em produção.
A consulta foi executada numa transacção PostgreSQL com `SET TRANSACTION READ ONLY`, timeout de 15 segundos e rollback final. Foram observadas 296 tabelas fora dos esquemas de sistema, 88 registos de migrations e 105 ficheiros de migrations: 17 ficheiros não constam do registo consultado. A lista nominal está no inventário privado. Não foi executada qualquer migration, reparação ou instalação automática. Divergência de schema não equivale, por si só, à perda de uma funcionalidade anteriormente aceite.
O diagnóstico `portal:dev-check` passou os dez controlos apresentados: PHP, driver, chave, configuração PostgreSQL, pastas de execução, dependências frontend, ligação, PostGIS e tabelas principais. É um diagnóstico de arranque, não uma prova de produção ou dos percursos completos.
O `phpunit.xml` aponta para QA `investmoz_test:55441`; a classe base de Feature pode reconstruir o schema. Não se executou essa suite sobre a base configurada nem se reutilizou uma demonstração com dados como base descartável. Os testes desta entrega interceptam acesso à base e transporte de email.
## Compatibilidade das recomendações
As classificações seguintes referenciam os requisitos da matriz existente, sem criar outro backlog ou alterar aceitações anteriores.
### Usabilidade e navegação
1. **Adaptável — cabeçalho** (`AUD-LEG-02`, `AUD-LEG-07`; DOCX p. XML 7–14, 352–379 e 524–551). `portal-hero.tsx` contém uma cópia acessível, uma cópia invisível que reserva o espaço final e texto animado marcado `aria-hidden`. A repetição numa extração textual não demonstra repetição visual. Não foram removidas essas protecções nem acrescentado CSS com `!important`. Falta observar o resultado em desktop, móvel e movimento reduzido.
2. **Adaptável — indicador provincial** (`AUD-GEO-03`; p. XML 445–478 e 617–650). `HomeController` conta actualmente valores distintos de `opportunities.province` nas oportunidades públicas. A leitura encontrou seis oportunidades públicas, cinco valores distintos, seis referências territoriais principais e nenhuma localização adicional. Não reproduz o valor externo «1», nem justifica fixar «11». O código não agrega `opportunity_locations`: permanece uma lacuna concreta para oportunidades multiterritoriais. Reutilizar o contrato territorial de `InvestmentCatalogue::locationPresentation`, distinguindo implantação, declaração e contexto de corredor; avaliar o custo das consultas antes de substituir a agregação SQL por leituras por registo. Não foi introduzida uma implementação com consultas por oportunidade apenas para corrigir o número.
3. **Aplicável — aliases antigos** (`AUD-LEG-06`; p. XML 380–409 e 552–581). `/simulador-fiscal` e `/apoio` já são rotas oficiais; `/Simulador` também existe. Os aliases minúsculos `/simulador` e `/contactos`, com ou sem barra final, não eram encontrados pelo router. Foram acrescentados redireccionamentos 301 para os destinos oficiais, conservando os parâmetros de consulta. Não se criou outro simulador ou formulário.
4. **Já implementada em parte — contactos** (`AUD-HUMAN-01`; p. XML 92–109). Há email institucional configurado e o rodapé contém contactos. Os campos específicos de apoio estão vazios; por isso a página de apoio ainda apresenta a indicação de contactos directos não publicados. Não é ausência total de contactos no portal. Não se copiaram os endereços de exemplo do DOCX para a configuração de apoio.
5. **Dependente de validação específica — identidade e contactos de apoio** (`AUD-HUMAN-01`). `portal.operator_name` está vazio. A propriedade da Tempo está decidida em `AGENTS.md`; os contactos dedicados, horários e designação a apresentar devem reutilizar os dados e decisões existentes que lhes correspondam. Os números e morada já no rodapé não foram certificados de novo nem alterados nesta tarefa.
6. **Aplicável, por demonstrar — móvel e teclado** (`AUD-LEG-02`, `AUD-LEG-07`). Há componentes e provas históricas; falta conferir o delta actual em navegador. Não se repetiram suites históricas ou se declarou acessibilidade integral com base nos testes de rotas.
### Oportunidades e publicação
7. **Já implementada em parte — fichas e campos** (`MASTER-9dee08d0-0074`, requisitos de oportunidades existentes; p. XML 174–211, 697–1143). O modelo já tem montante/moeda, promotor, maturidade, localização e última validação; há gestão, recursos, revisão de origem e contratos canónicos. Comparar as variantes de licenciamento, fonte/confiança e referência temporal dentro desses contratos antes de criar campos ou tabelas.
8. **Já implementada — capacidade de destaque** (gestão de oportunidades existente). `is_featured`, filtragem por publicação e apresentação de até seis destaques já existem. A leitura encontrou três oportunidades públicas assinaladas. Não foram promovidos projectos para preencher o ecrã. A alegada secção vazia depende do ambiente e da apresentação efectiva.
9. **Rejeitada — preenchimento comercial inventado** (p. XML 190–211 e modelos SQL seguintes). Não converter montante desconhecido em «A negociar», CAPEX fictício, calendário, licença ou promotor inventados. Nenhum dado foi enriquecido automaticamente.
10. **Adaptável — origem, confiança e rastreabilidade**. Reutilizar revisão de origem, recursos, versões e auditoria existentes. Um novo campo só é necessário depois de localizar uma lacuna no contrato correspondente; não foram duplicadas entidades ou relações.
### Simulador e acervo jurídico
11. **Já implementada em parte — simulador e recuperação** (`AUD-SIM-01`, `AUD-SIM-02`). Existem serviços e testes fiscais/financeiros, rascunhos e consumidores documentais. A avaliação externa também reconhece a integração. Não se declarou ausente nem se executaram novamente todos os ciclos anteriores.
12. **Adaptável — regras versionadas e resultados explicáveis** (`AUD-SIM-02`; p. XML 1183–2533). A intenção é compatível; deve ser aplicada no motor existente, com âmbito, período e citação. As duas versões repetidas da proposta não são duas funcionalidades novas.
13. **Rejeitada como implementação directa — API Node/Express, novas tabelas fiscais e seeds** (p. XML 2547–4667). Os exemplos não foram instalados. Os blocos sucessivos de seed e correcção não constituem uma única especificação aprovada. As taxas, benefícios, elegibilidade e percentagens do documento não foram importados nem tomados como legislação vigente.
14. **Dependente de conferência específica — divergências fiscais** (`AUD-SIM-02`). Confrontar cada parâmetro que se pretenda alterar com o acervo oficial já aceite, identificando diploma/artigo, período e regra afectada. Não é necessário revalidar genericamente a credibilidade das fontes oficiais. Não foi emitida conclusão jurídica ou alterado cálculo fiscal nesta entrega.
15. **Já implementada em parte — biblioteca e fontes** (`AUD-LIB-01`, `AUD-LIB-02`). Há biblioteca, controlo de publicação, classificação, scan, importação e vínculos; publicação documental não activa um cálculo. A existência de código não certifica todos os documentos ou direitos de divulgação.
### Balcão e pré-dossiê
16. **Adaptável — pré-dossiê no mesmo Balcão** (`AUD-DESK-01`, `AUD-DESK-02`; p. XML 4692–5968). Existem dossier, pedidos, editor de serviços, contexto, histórico e regras de titularidade. Reutilizar esses recursos e os rascunhos existentes; o wizard de cinco passos do DOCX não substitui o percurso adaptativo aprovado pela Tempo.
17. **Adaptável, com saldo por demonstrar — pré-preenchimento, guardar/retomar e exportação**. Conferir o consumidor e a versão do serviço concretos. Não foi criado outro cadastro, protocolo, PDF, tabela ou conta. As provas anteriores de dossier/pedido permanecem válidas no seu âmbito.
18. **Rejeitada — equivalência a acto público**. O pré-dossiê não é aprovação, submissão oficial, certificação ou benefício concedido. Não foram acrescentados logótipos, parceiros, SLAs ou integração institucional presumida.
19. **Dependente de confirmação técnica/institucional — integração directa com APIEX**. Nenhuma ligação, credencial, API ou acordo foi inferido do documento.
20. **Rejeitada como taxa actual — fallback cambial fixo** (p. XML 5039–5104). Reutilizar fontes e mecanismos já existentes, com data/natureza da taxa; valores de recurso e resultados demonstrativos não foram activados.
### Segurança, desempenho e divulgação
21. **Já implementada em parte — validação, autorização e controlo de abuso**. Há middleware, perfis/MFA, políticas, validação no servidor, throttling e controlos documentais. Os quatro testes de contacto reutilizados verificam validação, transporte simulado, erro de envio e escape de conteúdo; não certificam a segurança integral do portal.
22. **Adaptável — imagens, SEO, sitemap, redes móveis e acessibilidade**. O cabeçalho já utiliza WebP e imagens móveis. Não se aplicou conversão indiscriminada de imagens nem um schema SEO sugerido sem conferir o seu significado. Core Web Vitals, carga sustentada e navegação visual actual não foram medidos.
23. **Dependente do ambiente — HTTPS e debug**. O ambiente local anuncia HTTPS e debug activo. O HEAD HTTPS não concluiu a negociação TLS porque Schannel não conseguiu verificar a revogação do certificado (`CRYPT_E_NO_REVOCATION_CHECK`). Não se desactivou essa verificação. Isto não demonstra uma falha de certificado do domínio público. HSTS/debug em produção exigem inspecção da configuração efectiva desse destino, sem deploy nesta tarefa.
24. **Rejeitada como instalação automática — reCAPTCHA, CDN ou transmissão externa de dados pessoais**. Não se adicionou fornecedor externo, tracking, upload ou integração para satisfazer uma recomendação genérica. Conferir primeiro as protecções e necessidades existentes.
## Alteração realizada
`routes/web.php`: três linhas acrescentam os dois aliases, com 301, resolução por nome das páginas existentes e conservação de query string. Um parâmetro chamado `redirect` mantém-se um parâmetro de dados e não escolhe o destino do redireccionamento.
`tests/Unit/PortalLegacyNavigationTest.php`: quatro variantes de caminho e um caso de conservação de contexto/destino. A ligação à base é proibida pelo mock. Os testes exercitam a colecção de rotas e a execução do redireccionamento; não executam o percurso completo através do middleware ou de um navegador.
A matriz existente recebe apenas referências e evidências nos IDs relacionados. Estados de aceitação, critérios e restantes células anteriores são preservados. O backlog e o manual existentes apontam para esta entrega.
## Verificações e limites
- Reprodução antes da correcção: cinco casos novos falharam por rota inexistente.
- Após a correcção: nove testes dirigidos passaram, com 37 asserções, incluindo os quatro testes de contacto já existentes. Transporte de email simulado; nenhum email real foi enviado.
- PHP: sintaxe de `routes/web.php` válida.
- Pint: teste novo aprovado. O ficheiro de rotas apresenta seis categorias de formatação já presentes na cópia obtida do commit inicial. Não se reformatou todo o ficheiro alheio ao delta.
- Consulta PostgreSQL: `transaction_read_only=on`, rollback final. Sem alterações de negócio, migrations, concessões, seeds, contas, configurações fiscais ou dados reais.
- Navegador: o acesso CUA devolveu indisponibilidade do host. Não há captura nova, prova visual ou revisão responsiva desta entrega.
- HTTP externo ao processo: as duas tentativas HEAD HTTPS foram impedidas pela verificação de revogação local. Não se afirma uma verificação HTTP ponta a ponta dos destinos.
## Reversão
Retirar apenas o ciclo dos aliases acrescentado após a rota `/Simulador`; a cópia original está no registo privado. O teste novo e estas referências documentais podem ser removidos individualmente. Não usar `git reset --hard`, não repor integralmente um ficheiro que entretanto receba alterações de terceiros e não executar rollback de migrations: nenhuma foi realizada. A reversão não exige alterar dados ou permissões.
## Continuação delimitada
Continuar o P0 pelo contador provincial: contrato de definição visível, agregação multiterritorial com respeito pela publicação e pelas fontes territoriais existentes, testes de duplicação/sem localização/contexto e conferência só de leitura. Não substituir o número por um total nacional fixo nem aceitar uma implementação com consultas por oportunidade sem avaliar o impacto. Retomar o cabeçalho e os percursos em desktop/móvel quando o acesso ao navegador estiver disponível.
As fases estruturais, fiscais e de pré-dossiê devem partir desta reconciliação e dos IDs existentes. A directiva pede apresentação do resultado da Fase 0/P0 antes dessas alterações; esta entrega apresenta os achados e o primeiro delta seguro, com o saldo P0 explícito. Não há deploy, publicação, aceitação ou conclusão global presumida.
@@ -1,5 +1,17 @@
# Roteiro executável de interface — I0–I3 QA
## Tradução transversal — levantamento e plano, 02/11/2026
O pedido actual determina a tradução nas quatro línguas já apresentadas no selector: português, inglês, francês e espanhol. A referência anterior MASTER-9dee08d0-0008 prevê pelo menos português e inglês; o pedido actual explicita as quatro línguas já expostas. O levantamento é de código, não comprova a situação no ambiente activo nem substitui os recibos de reposição ou a comparação com o último estado demonstrado.
- Existe um contexto de idioma persistido no navegador e um dicionário em `tempo-brand.tsx`; a sua cobertura anterior concentrava-se no menu e rodapé. A tradução devolve o original quando não encontra uma chave. Os componentes institucionais `Trans` recorrem ao mesmo dicionário, mas várias das suas frases não têm entrada.
- Há 125 ficheiros em `resources/js/pages` (incluindo ficheiros auxiliares): páginas públicas, autenticação, gestão, oportunidades, carteira, percurso, dossier, simulador, terra, parcerias e outras áreas contêm textos portugueses directos. Formulários e componentes partilhados em `resources/js/lib` e `resources/js/components` também contêm rótulos e mensagens directos. A IA possui selector e textos próprios em português/inglês, sem integração completa com o idioma global. Catálogos e questionários de API têm pontos fixados em `pt-MZ`; mensagens de validação Laravel estão em português. Documentos, dados cadastrados, fontes e exportações precisam de tratamento distinto da interface e preservação do original.
- Incremento de código preparado: idioma do documento HTML acompanha o selector; textos comuns do acesso ao conteúdo, caminho de navegação, aviso de homologação, contacto de apoio e selector passam pelo contexto; algumas etiquetas institucionais passam a ter entradas nas três línguas. Os títulos sem entrada continuam no original. Esta cobertura é parcial, não representa conclusão do requisito nem publicação no activo.
- Continuação: etiquetas e placeholders de acesso e registo, apresentação do respectivo título, controlo acessível de visualização da palavra-passe e duas mensagens exactas de autenticação usam o mesmo contexto. Mensagens variáveis, restantes validações, outros ecrãs de autenticação e conteúdos de QA continuam por traduzir. Valores submetidos e comportamento de autenticação não foram alterados. O `types:check` e o build do primeiro incremento passaram; este acréscimo requer nova verificação.
- Comparação com `D:\Projects\revistatempo` e `D:\Projects\Tempo_Investidor\tempo-api`: a Revista usa componentes/hooks explicitamente marcados, POST `/api/translate`, protecção de tokens e cache; a API chama LibreTranslate e devolve o original em caso de falha, inclusive com HTTP 200. Não se demonstrou cobertura integral automática na Revista. Adaptado o cliente para os textos institucionais públicos já marcados com `Trans`, com URL configurável por `VITE_TRANSLATION_API_URL`, preservação de tokens, deduplicação e cache em memória apenas para respostas diferentes do original. Este incremento não envia dados de formulários, dossiers ou outras informações privadas para o serviço. A conectividade real, cobertura restante, resultados e exportações continuam pendentes de verificação e implementação; não declarar a plataforma traduzida.
Sequência restante: (1) inventariar por rota e componente, comparando com o último estado demonstrado e reutilizando provas e recibos existentes; (2) consolidar catálogo versionado e detecção de chaves ausentes para as quatro línguas; (3) migrar navegação e páginas públicas, autenticação e formulários; (4) migrar oportunidades, carteira, percurso adaptativo, dossier, simulador e áreas privadas/gestão sem alterar regras nem dados; (5) tratar mensagens do servidor, catálogos, conteúdos cadastrados e relatórios, preservando origens e indicando quando não existe versão traduzida; (6) verificar alternância, persistência, regressão dos fluxos, acessibilidade e exportações em QA isolado e registar cobertura por requisito nesta matriz e neste manual. Traduções de documentos oficiais não substituem os originais.
## C37 — causa confirmada; seleção de conta QA corrigida — P0, visual pendente
O pedido de reabertura referia a conta esperada18. A captura manual21:22–21:24UTC demonstra que foi selecionada a conta13 restaurada. O MFA foi alcançado, mas a autenticação nunca se completou. Causa concreta: credenciais MFA da conta13 cifradas pela chave de origem, incompatíveis com a chave estávelQA; a conta18 sintética não foi desafiada nessa tentativa. Nenhuma chave, MFA ou conta13 foi alterada para contornar o problema.
@@ -1,3 +1,441 @@
## 11/11/2026 — Energia incorporada pelo mesmo percurso de Mineração, em QA
**Implementação:** o sector existente `/sectores/energia-especifica` passa a apresentar o documento fornecido com7segmentos,11áreas de cadeia de valor,10projectos de referência,4riscos e24fontes com as ligações originais. Gás/upstream/construção/midstream/industrialização/mercado final,redes/hídrica,solar/baterias/eólica,hidrogénio,biocombustíveis e parques verdes mantêm o seu contexto. As três centrais solares aparecem uma vez cada,com capacidade,promotores,financiamento e estado referidos. Metoro conserva56milhõesUSD e47,3milhõesEUR,atribuídos às fontes,sem conversão ou escolha silenciosa. Previsão,operação,desenvolvimento,estudo e proposta não são tratados como estados equivalentes.
**Localização e continuidade:** filtros usam apenas bacias,corredores,localidades e províncias explicitamente referidos. Buzi não recebe província por aproximação;Maputo permanece por precisar quando não discriminado;hidrogénio/biocombustíveis sem implantação mantêm local por definir. Escolher área → seleccionar dossier existente ou criar → guardar → recuperar → usar como evidência na preparação/SWOT. Energia e Mineração reutilizam o mesmo serviço/componente de referências do sector e o mesmo escritor/histórico do dossier,sem módulos,tabelas,roles ou permissões novos. Podem coexistir no mesmo dossier sem substituir escolhas deliberadas. Origem,linha/parágrafo,versão,actor e data preservados. Referências não criam ofertas,concursos abertos,direitos,licenças,promotores ou conclusões SWOT.
**QA:** sete casos nativos dos dois sectores/166asserções no cluster55441,em transacções revertidas;cinco casos de interface com filtros,retoma,continuidade e locais por definir;nove casos já dirigidos aos componentes afectados,sem repetir operações aceites. Análise PHP,tipos,lint e compilação registados no fecho em `outputs/sector-resource-20261111/verification.json`. HTMLs dos componentes reais emSSR são evidência de renderização,não revisão visual de navegador. Documento deEnergia intacto,SHA256 `2d9d94ac70115ade43c4093a4d5b302aaa93623a149f936615c38ba67b1e2bd2`;extracção reproduzível em `tools/prepare-energy-reference.py`. As referências legais permanecem documentais;nenhum cálculo,benefício ou regra é activado pela incorporação.
**Saldo:** Mineração e Energia implementadas emQA,agrupadas para entrega Git com backup/restauro/reversão;instalação e conferência visual pendentes. Referência da homologação6d3bf293 e270instituições preservadas. Não há publicação de projectos reais nem envio de dados a terceiros. A fonte precisa de dados específicos para ofertas concretas e localização mais exacta;isso não impede consultar/preparar estes percursos. Restantes módulos e resíduos fiscaisL1–L7,L8,L9–L16 mantêm o seu saldo anterior.
## 11/11/2026 — Mineração e cadeia de valor implementadas em QA, sem instalação
O documento «Minérios de Moçambique e a sua localização» foi integrado na página existente `/sectores/mineracao`, sem módulo paralelo: **11 grupos minerais,10 áreas de cadeia de valor,6 projectos de referência,12 indicadores e13 fontes**. Consulta por localização,grupo mineral ou actividade; cada área mostra o âmbito territorial,possibilidades de fornecimento,logística,energia,beneficiamento ou transformação. Produção,capacidade,recursos e previsões conservam período e atribuição. São referências para preparação,não uma oferta de concessão ou projecto disponível.
**Percurso demonstrado:** sector → Cabo Delgado/Grafite → cadeia de valor → escolher dossier existente ou criar → confirmar → recuperar no mesmo dossier → referência disponível como evidência SWOT. A escolha conserva grupo mineral,área,localização,texto territorial,versão/hash da fonte,actor e data. Áreas distintas são acrescentadas sem duplicar o sector,alterar o título ou substituir escolhas anteriores. Repetição recupera a mesma referência; chave de criação reutilizada com outra selecção recusa409. Dossier alheio403,encerrado409,fonte/local/grupo incompatíveis409/422. Sem coordenadas,direitos mineiros,promotores ou relações geográficas inferidos. “Maputo” mantém província/cidade por precisar quando o original não distingue. A localização agregada de um grupo não prova a ocorrência de todos os minérios em todos os locais.
**QA dirigido:** quatro casos nativos PostgreSQL55441/82asserções,em transacções revertidas; três casos de interface para filtros,retoma e continuidade nas duas escolhas de dossier; nove casos dos componentes afectados reutilizados sem repetir operações aceites. Análise PHP,tipos,lint e compilação no fecho registados em `outputs/mining-reference-20261111/verification.json`. Não é conferência visual de navegador. Original intacto,SHA256 `7202fb5986b40e62347867c653770030683ffec2701a2921e84342f15e9f3d8a`; extracção reproduzível em `tools/prepare-mining-reference.py`,linhas de origem conservadas. Instruções contidas no documento não foram convertidas em regras de produto.
**Estado:** implementado em QA; instalação deste conteúdo ainda pendente de autorização específica da entrega. Homologação de referência `6d3bf293`,270instituições publicadas e entregas aceites preservadas. Não se criam/publicam dez oportunidades concretas nem se importam anexos para a biblioteca. Faltam instalação e conferência visual deste recorte; projectos concretos,localizações mais precisas e condições dependem dos respectivos dados. O restante saldo integral por módulo e fiscalL1–L7,L8 eL9–L16 permanece aberto nos seus recortes.
## 11/11/2026 — perfis uniformes e menu de seis grupos instalados em6d3bf293
Instalação expressamente autorizada deeca67191 concluída na revisão integrada `6d3bf2937ee6f5a58267967516560ba73d2cc936`,conferida às12:38:49Africa/Johannesburg(10:38:49UTC). A guarda inicial detectoua400802b já instalada e parou antes de escrever. Comparação delimitada identificou seis alterações independentes de tradução/cabeçalho fixo,integradas e preservadas;os quinze ficheiros aprovados deeca67191 permaneceram idênticos. Pacote reconciliado aplica/reverte exactamente sobrea400802b;pacote original preservado. Não foi necessária nova decisão de âmbito.
Utilizável em `/fundos` e nos detalhes deQuorus/Moza:mesmo componente público,descrição e produtos uma vez,categoria/tipo numa linha,contactos e moradas conhecidos sem duplicação;sem secções vazias,avisos genéricos,preços antigos ou notas internas. Menu principal **Início–Descoberta–Realização–Suporte–Sobre nós–Mais**,com concursos emDescoberta e acessos de trabalho preservados emMais. Fontes,históricos,contactos e moradas originais continuam no editor;nenhuma deduplicação de dados foi executada.
Backup novo/restauro isolado conferidos:190tabelas/369ficheiros. Vinte e um ficheiros por hash(15aprovados+6preservados);revisão,entrada,up,login,Concursos,fundos e simuladorHTTP200. Configuração intacta,zero migrações/importações/republicações. Totais/checksums idênticos ao backup nas sete tabelas protegidas:funding_programmes,funding_programme_versions,funding_programme_interests,desk_cases,simulation_form_drafts,labour_demands,training_capacities.275registos/270instituições publicadas preservados. Recibo `outputs/funding-uniform-menu-20261111/installation.json` e reconciliação no mesmo directório. Pacote `outputs/entrega-financeiras-menu-reconciliada-20261111.zip`,SHA256 `dc0be0d20569a96a3338ba140b8430013f146deb6eafd73b66343c2841d91e16`;reversão paraa400802b,preservando trabalho alheio.
Este conjunto deixa de ser pendência de implementação/instalação;conferência visual permanece separada. Não requer seleccionar/publicar novamente instituições. Restante saldo de módulos e fiscal conservado;portal não declarado integralmente concluído.
## 11/11/2026 — uniformização Quorus/Moza e menu de seis grupos em QA
Correcção orientada pelos exemplos expressos do proprietário:lista e detalhe reutilizam agora **o mesmo componente público**. Descrição única,categoria/tipo numa linha,produtos/serviços uma vez,contactos e moradas apenas quando disponíveis. Quorus:descrição de corretagem eSeguros uma vez;sem secções vazias ou avisos genéricos. Moza:descrição paraPME,CréditoPME/instrumentos uma vez,umtelefone e umamorada;sem notas de publicação,preços antigos,comissões/garantias por reconfirmar,entidade responsável repetida,sectores/cobertura desconhecidos ou referências institucionais duplicadas. Não inventa dados nem altera condições guardadas. Ligações dos produtos/contactos permanecem acessíveis uma vez;moradas diferentes são preservadas.
Menu principal exactamente **Início–Descoberta–Realização–Suporte–Sobre nós–Mais**. Concursos movido paraDescoberta;Biblioteca eInstituições financeiras com os nomes pedidos;conteúdos deSobre nós/Mais preservados. Acesso de trabalho deslocado paraMais,sem perder permissões;conta/contexto/licenciamentoS08 e filtros preservados. Instruções permanentes actualizadas com o padrão pedido.
**QA:**33verificações dirigidas aprovadas,com três casos novos:exemplos públicos exactos nas duas páginas,deduplicação conservadora e cabeçalho real com seis entradas em quatro combinações(público/autenticado,móvel/computador). Reutilizadas provas dos fluxos de interesse/dossier/editorial e das permissões,não reexecutadas na base. Fontes de270instituições intactas,sem migração/importação/republicação. Artefactos `outputs/funding-uniform-menu-20261111/verification.json` eHTMLs reais emSSR. Não é conferência visual de navegador;tipos/lint/compilação aprovados;pacote Git com aplicação/reversão conferidas no fecho.
**Estado:** implementado em QA,não instalado;homologação mantém `a59142ca` e270instituições publicadas. Restam instalação e conferência visual;não requer seleccionar/publicar novamente instituições. Restante saldo de módulos e fiscal preservado. Próximo trabalho:entregar este conjunto compatível e continuar os resíduos de implementação acordados.
## 11/11/2026 — apresentação financeira profissional instalada ema59142ca
Instalação expressamente autorizada,executada pelo processo Git existente e conferida às11:38:54Africa/Johannesburg(09:38:54UTC): `a59142caa2fc277975be82fb550223ff7d949594`. O directório `/fundos` e o detalhe apresentam descrição e produtos/serviços conhecidos,sem usar texto de importação,fichas ou documento gerador como apresentação. Descrições específicas são preservadas;resumo genérico exacto passa a uma descrição pela natureza publicada. Não são inventadas ofertas para instituições sem produtos específicos identificados. Metadados/fontes/histórico mantidos no editor;ligações institucionais e datas úteis acessíveis separadamente. Orientação transversal registada emAGENTS.
Backup novo/restauro isolado conferidos:190tabelas/369ficheiros. Dezasseis fontes por hash;revisão,entrada,up,login,Concursos,fundos e simuladorHTTP200. Configuração intacta,zero migrações/importações/republicações do lote. Totais/checksums idênticos ao backup nas sete tabelas protegidas:funding_programmes,funding_programme_versions,funding_programme_interests,desk_cases,simulation_form_drafts,labour_demands,training_capacities.275registos/270instituições publicadas preservados. Recibo `outputs/funding-public-editorial-20261111/installation.json`;pacote `outputs/entrega-financeiras-editorial-20261111.zip`,SHA256 `531db038a46ee32606ebbed0aab43630e2f68b05569ed3a6977b85b59ea68b8b`. Revisão8f295f05 preservada para reversão.
Este delta deixa de ser pendência de implementação/instalação. Visual permanece separado,enriquecimento de produtos específicos depende de informação disponível. Não é necessário seleccionar/publicar novamente as270instituições. Restantes módulos e resíduos fiscais conservam o saldo existente;portal não declarado integralmente concluído.
## 11/11/2026 — apresentação pública profissional do directório financeiro em QA
**Pedido do proprietário:** descrições profissionais e apelativas,com descrição/produtos/serviços úteis ao investidor,sem texto de fichas,importação ou documento gerador;orientação transversal registada emAGENTS. O directório e o detalhe passam a uma apresentação editorial comum:reconhece exclusivamente o resumo genérico exacto da importação e usa a natureza publicada da entidade;descrições editoriais específicas são preservadas e limpas da fórmula genérica final. Produtos identificados são apresentados pelos nomes/instrumentos existentes;os sete catálogos específicos disponíveis não são extrapolados para outras entidades. Nas restantes,apresentam-se áreas de serviço conhecidas ou contacto para conhecer soluções,sem inventar produtos/condições.
Retirados da apresentação pública nomes de ficheiros,hashes,linhas de importação,códigos de sucessora e descrições de registo interno. Ligações institucionais e datas úteis continuam acessíveis;metadados e fontes permanecem intactos no cadastro/editor. Instituições históricas/fundidas continuam identificadas como tal. Instituições sem chamada não mostram uma secção vazia de candidatura. Textos da selecção guardada e chamadas públicas dePorque investir/Serviços ajustados à linguagem do investidor. Fluxos de interesse,dossier,edição,revisão/publicação e filtros preservados.
**QA:** nove verificações afectadas aprovadas,três novas incluindo270referências fornecidas,descrições específicas preservadas,produtos não inventados e renderização dos componentes públicos reais de lista/detalhe. Sem escrita em dados activos ou nova publicação do lote;é uma projecção editorial dos dados já publicados. Evidência `outputs/funding-public-editorial-20261111/verification.json` e quatroHTMLs;isto não constitui conferência visual no navegador. Tipos/lint/compilação aprovados;pacote Git com aplicação/reversão conferidas no fecho.
**Estado:** implementado em QA,não instalado;homologação mantém `8f295f05` e270instituições publicadas. Restam instalação/visual e enriquecimento de produtos específicos quando houver informação disponível. O saldo dos outros módulos e resíduos fiscais permanece inalterado. Não é necessário seleccionar ou publicar novamente as270instituições para esta alteração de apresentação.
## 11/11/2026 — rectificação do volume PEC instalada em8f295f05
Instalação expressamente autorizada,executada pelo processo Git existente e conferida às11:03:24Africa/Johannesburg(09:03:24UTC): `8f295f05510c786afeb8ea1dfa692e08d90a0d30`. No simulador fiscal,L8,**Rectificar volume na simulação PEC** conserva o valor original e o corrigido,com escolha explícita/fonte e efeito na estimativa apresentados no relatório. A hipótese de tesouraria anterior exige reconciliação se o total mudar;nenhum comprovativo pago,dedução ou saldo oficial é duplicado. Não executa pagamentos nem submete declaração rectificativa.
Backup novo/restauro isolado conferidos:190tabelas/369ficheiros. Quinze fontes por hash;revisão,entrada,up,login,Concursos,fundos e simuladorHTTP200. Configuração intacta,zero migrações/importações. Totais/checksums idênticos ao backup nas sete tabelas protegidas:funding_programmes,funding_programme_versions,funding_programme_interests,desk_cases,simulation_form_drafts,labour_demands,training_capacities.275fichas/270instituições publicadas preservadas. Recibo `outputs/irpc-pec-correction-20261111/installation.json`;pacote `outputs/entrega-pec-rectificacao-20261111.zip`,SHA256 `559e4477059b092a193d990f7dfb6cb50007d43c24ec00e2484326f076e0c973`. Revisãoa708f079 preservada para reversão.
Este recorte29(3) deixa de ser pendência de implementação/instalação. Visual permanece separado;correcções fora do recorte têm âmbito próprio. Restam última base29(5),PPC excedente,períodos/regimes especiais e demais resíduos L1–L7/restanteL8/L9–L16 e módulos. Próximo trabalho:primeiro residual de simulação executável com as fontes aceites,sem reabrir este recorte. Portal não declarado integralmente concluído.
## 11/11/2026 — rectificação do volume PEC na simulação em QA
**Recorte29(3) avançado:** fonte documental permite rectificar o volume de negócios2025 usado na estimativa PEC2026. Valor original,valor corrigido,titular,exercício e fundamento ficam conservados;escolha explícita entre aplicar a correcção e manter o original. Não submete declaração rectificativa. A estimativa recalcula0,5%,limites e PPC anteriores uma única vez;relatório mostra origem,valor corrigido e efeito na estimativa. Mudança da base/titular/exercício exige reconferência;fonte ou valor ausente não é zero.
Cálculo→API privada→recuperação→HTML/relatório do mesmo dossier em cinco cenários:aumento40.000→90.000MZN,redução40.000→20.000,original conservado40.000,base divergente e correcção para volumezero indeterminados. Hipótese de caixa anterior40.000 deixa de reconciliar quando o total muda;não é sobrescrita. Saldo anual138MZN,comprovativos pagos e histórico anterior preservados. QA55441 existente:uma prova nativa/47asserções com rollback,cinco testes novos de cálculo/interface e40afectados aprovados;tipos/lint/compilação aprovados. Provas `outputs/irpc-pec-correction-20261111/*-verification.json`;pacote Git com aplicação/reversão conferidas no fecho. Não é conferência visual.
**Estado:** implementado em QA,ainda não instalado;homologação mantém `a708f079` e270instituições publicadas. Fonte:RIRPC,Decreto9/2008,art29(3),BR16/I16-04-2008,original já aceite SHA256 `fc373ff5637edf75ec6a9595dec8763a8503d4ee786e3b7d8f1c742eae9d80ee`. Sem migração,base,tabela ou direito novo. Restam instalação/visual,última base29(5),PPC excedente,períodos/regimes especiais e demais resíduos fiscais L1–L7/restanteL8/L9–L16. Correcções fora deste recorte conservam âmbito próprio;L8 e portal não são declarados integralmente concluídos. Próximo trabalho:primeiro residual de simulação executável pelas fontes aceites,sem reabrir este recorte.
## 11/11/2026 — finalidade de viabilidade e projecção PEC instaladas ema708f079
Instalação expressamente autorizada,executada pelo processo Git existente e conferida às10:45:50Africa/Johannesburg(08:45:50UTC): `a708f079b318a13100644c27287f1caa854ecf1d`. No simulador fiscal,L8,os ecrãs apresentam **Simular PPC/PEC**,estimativas fiscais e hipóteses de tesouraria para avaliar a viabilidade do negócio. A hipótese PEC permite distribuir valores por três meses ou concentrar a estimativa num mês escolhido,com soma conferida e prazo legal não certificado. Rascunho,recuperação,histórico e relatórios preservam escolhas;dados documentais já pagos permanecem distintos. O portal não executa pagamentos,nem submete declarações fiscais.
Backup novo/restauro isolado conferidos:190tabelas/369ficheiros. Vinte e quatro fontes por hash;revisão,entrada,up,login,Concursos,fundos e simuladorHTTP200. Configuração intacta,zero migrações/importações. Totais/checksums idênticos ao backup nas sete tabelas protegidas:funding_programmes,funding_programme_versions,funding_programme_interests,desk_cases,simulation_form_drafts,labour_demands,training_capacities.275fichas/270instituições publicadas preservadas. Recibo `outputs/irpc-pec-plan-20261111/installation.json`;pacote `outputs/entrega-simulacao-viabilidade-20261111.zip`,SHA256 `a4dbbe26ca72230e3ff7a5c2a744c187a93380a4857e522b1db5ee17acce3ca5`. Revisãobcbbe51c preservada para reversão.
Este delta deixa de ser pendência de implementação/instalação. Conferência visual permanece separada. Execução de pagamentos não é critério de conclusão deste simulador,conforme clarificação expressa registada emAGENTS. Próximo trabalho:primeiro residual de simulação executável pelas fontes aceites. Base29(5),rectificação29(3),PPC excedente,períodos/regimes e restantes resíduos L1–L7/restanteL8/L9–L16 e módulos mantêm o saldo;portal não declarado integralmente concluído.
## 11/11/2026 — finalidade do simulador e projecção de tesouraria PEC
**Clarificação expressa do proprietário:** o portal simula impostos para avaliar a viabilidade do negócio; não faz pagamentos. A apresentação anterior de «obrigação» e «plano de pagamento» criava uma expectativa errada de execução. Ecrãs PPC/PEC passam a **Simular PPC/PEC**,estimativa fiscal e projecção de tesouraria. Fontes legais e dados documentais já pagos continuam a informar o cálculo; não são operações executadas pelo portal. A execução de pagamentos não é pendência deste simulador.
**Implementado em QA:** hipótese PEC distribuída por Junho/Agosto/Outubro com montantes explícitos cuja soma corresponde exactamente ao total,ou hipótese concentrada num mês escolhido. Não inventa igualdade/arredondamento ou validação de prazo para a opção única. Valores desconhecidos,divergência da soma,total alterado e ausência de estimativa positiva invalidam a projecção dependente,sem bloquear o cálculo fiscal. Trocar hipótese conserva edições;resumo com Alterar. Rascunho privado,recuperação,histórico e relatório do mesmo dossier reutilizados. As estimativas não alimentam os comprovativos pagos nem alteram deduções anuais.
Cinco verificações novas de cálculo/interface e35afectadas aprovadas;percurso sintéticoAPI/recuperação/relatório em quatro cenários(distribuído,concentrado,divergente,total alterado),com saldo anual138MZN conservado. Provas `outputs/irpc-pec-plan-20261111/*-verification.json`. Sem migração,base,papel ou permissão nova. Tipos/lint/compilação aprovados;uma prova nativa/38asserções com rollback. Pacote Git e reversão conferidos no fecho. Isto não é conferência visual.
**Estado:** QA,ainda sem instalação;homologação permanece `bcbbe51c` e270instituições publicadas. Restam instalação/visual e recortes fiscais de base29(5),rectificação29(3),PPC excedente,períodos/regimes e outras regras de simulação;prazo legal da hipótese única não é certificado. L1–L7/restanteL8/L9–L16 e demais módulos permanecem no saldo,sem transformar o portal em sistema de execução fiscal. Próximo trabalho:primeiro residual de simulação executável com fontes aceites.
## 11/11/2026 — total e calendário PEC instalados embcbbe51c
Instalação expressamente autorizada,executada pelo processo Git existente e conferida às10:28:18Africa/Johannesburg(08:28:18UTC): `bcbbe51c2fc279d5b2d85412f7bcb6ca2423a9b6`. Utilizável no simulador fiscal,fecho anual L8: **Preparar obrigação PEC**,com total ordinário2026 baseado no volume documental2025,limites antes dos PPC pagos anteriores,mesesJunho/Agosto/Outubro e dispensa no ano de início efectivo. Guardar/recuperar pelo rascunho privado e apresentar no relatório do mesmo dossier. Não cria pagamento,dedução anual ou reembolso.
Backup novo/restauro isolado conferidos:190tabelas/369ficheiros. Catorze fontes por hash;revisão,entrada,up,login,Concursos,fundos e simuladorHTTP200. Configuração preservada,zero migrações/importações. Sete tabelas protegidas mantêm totais/checksums do backup:funding_programmes,funding_programme_versions,funding_programme_interests,desk_cases,simulation_form_drafts,labour_demands,training_capacities.275fichas/270instituições publicadas preservadas. Recibo `outputs/irpc-pec-obligation-20261111/installation.json`;pacote preservado `outputs/entrega-pec-obrigacao-revisada-20261111.zip`,SHA256 `a9d9bb089305003487695969d653c7f876a913fce44bbbffb5191c1a792a9184`. Revisãofbff2660 preservada para reversão.
Este recorte deixa de ser pendência de implementação/instalação;visual permanece separado. Restam montantes por prestação/pagamento único,última base29(5),rectificação29(3),PPC excedente,períodos/regimes especiais,guias/juros. Próximo trabalho:primeiro desses casos executável pelas fontes aceites,sem repetir o recorte instalado. L1–L7/restanteL8/L9–L16 e restantes módulos continuam no saldo;portal não declarado integralmente concluído.
## 11/11/2026 — total e calendário PEC ordinário em QA
**Novo recorte L8:** preparação do total PEC2026 pelo volume de negócios documental2025:0,5%,limites30.000/100.000MZN aplicados antes de deduzir PPC efectivamente pagos no ano anterior. MesesJunho/Agosto/Outubro,sem inventar dia limite ou importar o arredondamento PPC. Dispensa do ano de início efectivo da actividade29(4) fundamentada separadamente. Fonte:RIRPC,Decreto9/2008,art29(1–5),BR16/I16-04-2008,original SHA256 `fc373ff5637edf75ec6a9595dec8763a8503d4ee786e3b7d8f1c742eae9d80ee`, já preservado no acervo.
Formulário progressivo reutiliza titular/exercício e conserva valores ao ocultar campos sem aplicação; rascunho privado e relatório do mesmo dossier reutilizados. Total preparado não é PEC pago,nem cria dedução anual,reembolso,guia ou consumo de saldo oficial. Ausência de informação não equivale a zero. Sem volume no ano anterior exige base29(5);PPC superiores ao montante limitado ficam por reconciliar,sem obrigação negativa. Esses casos não impedem o cálculo anual independente.
**Percurso sintético:** cálculo→API privadaPOST/PUT→GET→recálculo→HTML/relatório:40.000MZN ordinário,20.000mínimo antes de PPC,90.000máximo antes de PPC,dispensa inicial0,volumezero e desconhecido indeterminados. Histórico e saldo anual138MZN preservados. QA55441 existente,uma prova nativa/56asserções com rollback,sem nova base/tabela/porta/papel/migração. Seis verificações novas de cálculo/interface e29afectadas aprovadas; tipos/lint/compilação aprovados. Provas `outputs/irpc-pec-obligation-20261111/*-verification.json`;pacote Git com aplicação/reversão conferidas no fecho.
**Saldo:** implementado em QA,ainda não instalado. Homologação mantém `fbff2660` e270instituições publicadas. Restam entrega Git e visual;montantes por prestação/opção de pagamento único,última base29(5),rectificação29(3),PPC acima do limite,períodos/regimes especiais,guias/juros permanecem abertos. L8 não é integralmente concluído;resíduos L1–L7/restanteL8/L9–L16 e restantes módulos conservados. Próximo trabalho:primeiro desses casos executável com as fontes aceites,sem reabrir os recortes entregues.
## 11/11/2026 — obrigação PPC instalada emfbff2660
Instalação expressamente autorizada e executada pelo processo Git existente: `fbff2660f280f53fe079221164bb21917032ac70`, conferida às09:04:59Africa/Johannesburg(07:04:59UTC). Utilizável no simulador fiscal,fecho anual L8: **Preparar obrigação PPC**,com imposto anterior documental líquido de retenções,80%,três prestações arredondadas individualmente por excesso ao milhar e calendárioMaio/Julho/Setembro. Guarda e recuperação pelo rascunho privado existente e inclusão no relatório do dossier. Obrigação preparada não equivale a pagamento,nem cria dedução de PPC.
Backup novo/restauro isolado conferidos:190tabelas/369ficheiros. Catorze fontes do delta por hash e revisão,entrada,up,login,Concursos,fundos e simuladorHTTP200. Configuração preservada,zero migrações/importações;sete tabelas protegidas com totais/checksums idênticos ao backup:funding_programmes,funding_programme_versions,funding_programme_interests,desk_cases,simulation_form_drafts,labour_demands,training_capacities.275fichas/270instituições publicadas preservadas. Recibo `outputs/irpc-ppc-obligation-20261111/installation.json`; pacote `outputs/entrega-ppc-obrigacao-20261111.zip`,SHA256 `ab72a11b9306e4503ab92669c47ec592b2510aa01189ebab1d5216bb40d3769a`. Revisão6805b606 preservada para reversão.
Este recorte deixa de ser pendência de implementação/instalação. Conferência visual permanece separada. Próximo residual fiscal:PEC/obrigações e outros casos executáveis com fontes aceites;anualização/períodos especiais,dispensas/guias/juros e resíduos L1–L7/restanteL8/L9–L16 mantêm-se explícitos. Restantes módulos permanecem no saldo existente;portal não declarado integralmente concluído.
## 11/11/2026 — obrigação PPC ordinária de L8 em QA
**Pendência avançada:** o fecho anual existente passa a preparar a obrigação PPC2026 com liquidação documental do exercício civil completo2025: imposto anterior21(1) líquido de retenções,80%,três prestações iguais arredondadas individualmente por excesso ao milhar de meticais. Calendário por mês:Maio,Julho,Setembro,sem inventar dia limite. Fonte:RIRPC,Decreto9/2008,arts27(1)(a)/28(1–3),BR16/I16-04-2008,original SHA256 `fc373ff5637edf75ec6a9595dec8763a8503d4ee786e3b7d8f1c742eae9d80ee`, já preservado no acervo. Não usa colecta projectada corrente como liquidação anterior.
Reutiliza titular/exercício e rascunho privado existentes; formulário progressivo conserva edições deliberadas ao trocar enquadramento. Obrigação preparada e comprovativos efectivamente pagos permanecem distintos: não cria pagamento,dedução,saldo oficial,guia ou juros. Campos vazios,fonte ausente,divergência de titular/período,base negativa e período especial ficam indeterminados,sem impedir o fecho anual independente. Zero explícito permanece zero. Anualização28(3),dispensas,PEC e restantes regimes ficam separados.
**Prova sintética:** cálculo→POST/PUT→GET privado→recálculo→HTML/relatório do mesmo dossier em cinco cenários:72.000MZN ordinário,75.000após arredondamento,zero,valor desconhecido e período especial indeterminados. Saldo anual138MZN e comprovativos inalterados; histórico anterior conservado. Uma prova nativa/47asserções com rollback no QA55441 existente; cinco verificações novas de cálculo/interface e24afectadas aprovadas. Tipos/lint/compilação aprovados; pacote Git com aplicação/reversão conferidas no fecho. Provas `outputs/irpc-ppc-obligation-20261111/*-verification.json`.
**Estado:** implementado em QA, ainda sem instalação. Homologação mantém `6805b606`,Educação entregue e270instituições publicadas. Restam entrega Git deste delta e visual; L8 não está integralmente concluído. Preservam-se os resíduos L1–L7,restanteL8/L9–L16 e restantes módulos. Próximo trabalho fiscal: PEC/obrigações e demais casos executáveis com as fontes aceites,sem reabrir PPC ordinário.
## 11/11/2026 — retoma da revisão de Educação instalada em6805b606
Instalação autorizada pelo utilizador, executada pelo processo Git existente e conferida às08:46:05Africa/Johannesburg(06:46:05UTC): `6805b606a106fbed410b191ed778c0fad7c8e913`. Guardar e recuperar a preparação privada da revisão interna de procura/capacidade fica utilizável na homologação, pelo detalhe existente em `/management/education`. A gravação privada não decide nem aprova; preserva notas, versão analisada e a chave de uma intenção incerta.
Backup novo e restauro isolado conferidos:190tabelas/369ficheiros. Catorze fontes conferidas por hash; revisão,entrada,up,login,Concursos,fundos e simuladorHTTP200. Configuração intacta, zero migrações/importações. Totais e checksums inalterados nas sete tabelas protegidas:funding_programmes,funding_programme_versions,funding_programme_interests,desk_cases,simulation_form_drafts,labour_demands,training_capacities. Permanecem275fichas/270instituições publicadas. Recibo `outputs/education-review-preparation-20261111/installation.json`; pacote preservado `outputs/entrega-educacao-retoma-20261111.zip`, SHA256 `d2b7fd5e0d24baffd7b53d534c78e6e0f4492b47a40bb1d9d146b8a8b553caf5`.
Este recorte deixa de ser pendência de implementação e instalação. Conferência visual continua pendente; jornadas externas educativas e representação/consentimento documentados permanecem em aberto. Resíduos fiscais L1–L7/restanteL8/L9–L16 e restantes módulos mantêm-se no saldo existente. Próximo trabalho: primeiro residual executável dessa ordem, sem repetir este recorte nem as provas aceites.
## 11/11/2026 — retoma durável da revisão de Educação em QA
A revisão interna de procura e capacidade pode guardar e recuperar a preparação privada, incluindo notas, versão analisada e intenção de decisão incerta. Reutiliza os eventos de auditoria existentes, com conteúdo cifrado e isolamento por conta, tipo e declaração. Guardar não decide: a decisão continua pelo escritor nativo, com revisão independente e validação de versão. A intenção fica persistida antes do envio; uma resposta perdida recupera a mesma chave, sem duplicar a decisão. Edições posteriores e preparações mais recentes noutra janela não são substituídas silenciosamente.
**QA concluído, ainda não instalado:** cinco testes nativos/105 asserções e dez testes novos de interface, com os 17 afectados preservados; análise PHP e tipos aprovados. A página nativa recuperada foi exercitada no componente real em harness: recibo reconhecido, edição posterior conservada e zero envios adicionais. Prova `outputs/education-review-preparation-20261111/verification.json`. Isto não constitui conferência visual no navegador. Sem migração, tabela, papel ou concessão nova.
**Saldo:** falta entregar este delta pelo processo Git e conferir a apresentação no uso normal. Jornadas externas de estudantes, formadoras e empregadores, representação e consentimento documentados permanecem pendentes; Educação não é declarada integralmente concluída. A homologação mantém `9c067d6a` e as 270 instituições publicadas. Permanecem explícitos os resíduos fiscais L1–L7/restante L8/L9–L16 e os restantes módulos. As provas aceites não foram reabertas.
## 11/11/2026 — combinação CBF15/créditos/PEC instalada em9c067d6a
Instalação expressamente autorizada nesta retoma e executada pelo processoGit existente: `9c067d6ae89c0cd1e9ad4f2813e84048988d3319`, conferida às07:43:38Africa/Johannesburg(05:43:38UTC). Dezficheiros do delta conferidos por hash; revisão,entrada,login,up,Concursos,fundos e simuladorHTTP200; configuração preservada, zero migrações/importações. Backup/restauro isolado conferido:190tabelas e369ficheiros. Nenhum restauro sobre dados activos.
**Utilizável na homologação:** combinação CBF15corrente/transportado com créditos económicos/internacionais e saldos anteriores, com escolha dePEC limitada à colecta residual apósCBF15; gravação/recuperação e relatórios pelo percurso existente, conforme cinco cenáriosQA aceites. Os recortes anteriores de transporte e os históricos são preservados. Nenhum saldo oficial é consumido, pagamento/reembolso executado ou nova prioridade entre exercícios inventada.
**Preservação activa comprovada:** totais e checksums idênticos ao backup nas tabelas `funding_programmes`, `funding_programme_versions`, `funding_programme_interests`, `desk_cases` e `simulation_form_drafts`.275fichas,270publicadas; o original financeiro continua disponível. Não se extrapola esta comparação para tabelas voláteis de sessão/cache. Prova `outputs/irpc-cbf15-combination-20261111/installation.json`; pacote e reversão `outputs/entrega-irpc-combinacao-20261111.zip`, SHA256 `cebcd063b06f70a33b57f6cdd710478fb7aa3d18ef64aec4489cf855064bff99`.
Saldo: este recorte deixa de ser pendência de implementação/instalação. Conferência visual mantém-se separada; continuam repartição automática/múltiplos projectos, outros períodos/moedas/regimes/EP/cumulações/benefícios/formulários e os restantes resíduos L1–L7/restanteL8/L9–L16 e módulos não fiscais. O portal não é declarado integralmente concluído. A próxima implementação seguirá o primeiro residual executável do saldo existente, sem reabrir a combinação entregue nem a publicação das270instituições.
## 11/11/2026 — combinação CBF15 com créditos económicos/internacionais e PEC em QA
Retoma da ordem de conclusão existente, sem nova auditoria ou repetição da publicação financeira:270instituições publicadas e revisão instalada4f3dbcf8 preservadas; visual financeiro/S08 permanece separado. Reconciliação delimitada: o transporte CBF15 básico(7476dc34) já integra a revisão instalada4f3dbcf8, pelo histórico Git e recibos aceites; a nota antiga «QA, não instalado» desse transporte foi superada por entregas posteriores. Não reinstalar esse recorte como se estivesse ausente.
**Implementado nesta passagem, ainda sem instalação:** retirada a recusa técnica genérica da combinação de CBF15 corrente/transportado com saldos anteriores, reutilizando as fontes e ordem de dedução existentes. Créditos económicos e internacionais/correspondentes transportes precedemCBF15; PEC corrente e anterior usam apenas a colecta residual seguinte, antes de retenções/PPC. Uma escolha explícita de PEC superior ao residual é recusada como indeterminada, sem reduzir silenciosamente parcelas ou fabricar saldo recuperável. Histórico e fontes de investimento/uso anterior continuam preservados; a chave da combinação inclui versão própria para exigir recálculo dos resultados anteriores desse ramo, sem reescrever históricos nem invalidar os recortes sem combinação.
Cinco cenários sintéticos: combinação ordinária138MZN; quota residual documental100→saldo438; PEC sobrealocado→saldo indeterminado; CBF15 esgota colecta1338 ePEC seleccionado expressamente0→saldo−500 proveniente dePPC, sem reembolso/consumo oficial; janelaCBF15 expirada→saldo538. Também provada a varianteCBF15 corrente e o limite de quota após créditos anteriores. Cálculo→POST/PUT do rascunho privado existente→GET→recálculo exacto→HTML/dossier aprovados no QA55441/proactive_investment_20261027_test, com rollback e sem novas bases/portas/migrations/direitos. Uma provaAPI/37asserções; cinco testes novos nas combinações e23casos afectados de compatibilidade aprovados, não multiplicados como funcionalidades. Tipos/lint aprovados; compilação QA e pacote registados no fecho. Provas `outputs/irpc-cbf15-combination-20261111/*-verification.json` e relatórios recuperados.
**Saldo:** implementação e percurso desta combinação concluídos emQA; instalação do novo delta e visual separados. Persistem repartição automática/múltiplos projectos, outros períodos/moedas/regimes/EP/cumulações/benefícios e documentos fora dos recortes demonstrados; L1–L7/restanteL8/L9–L16 não são encerrados. Os cinco programas anteriores privados do directório financeiro não são publicados por consequência desta entrega. Próximo trabalho: agrupar a entrega compatível deste delta e continuar o primeiro residual fiscal executável, preservando os fechos aceites.
## 10/11/2026 —270instituições publicadas e consultáveis na homologação
**Cadastro e disponibilização do documento fornecido concluídos em homologação.** Conferência de leitura às22:54:31Africa/Johannesburg(20:54:31UTC), revisão instalada `4f3dbcf86499180011fc79712d6b5be445e57e26`:270/270instituições/operadores do documento publicados, nenhuma referência do original em falta,270identidades únicas,393referências físicas e168contactos. Publicações confirmadas pelo fluxo editorial nativo do utilizador; zero escritas efectuadas pelo verificador. As275fichas totais incluem cinco referências anteriores de fundos/programas que permanecem privadas, sem serem publicadas por consequência deste lote. Fonte original/SHA/edição, incertezas e identidades preservadas.
Consulta pública comprovada porHTTP200 e dados efectivamente devolvidos: `/fundos?kind=institution&include_historical=1` contém270resultados; filtro categoria seguros/tipo seguradora/presença física/Nampula contém seis(Fidelidade,Emose,HollardMoçambique,Britam,Arko,Palma). Fichas deBCI,Absa eArko abrem no componente público e conservam a fonte SHA256 `119bb5514d2777849b70aebfec21d28a41885cbdc7d9827fbebd764fe994c108`. A opção de incluir histórico não transforma uma instituição fundida/extinta em activa. Sede/balcões continuam separados de cobertura e produtos; dados desconhecidos não se tornam condições confirmadas nem garantias de financiamento/seguro.
**Saldo ACT-20261029-12 neste recorte:** conteúdo do documento cadastrado, publicado pelo editor e disponível por localização/categoria/tipo; já não é página vazia ou apenas códigoQA. Continuidade banco/seguradora/balcão/fonte/versão no mesmo dossier, gravação/recuperação e conservação após alterações continuam cobertas pelas provasQA aceites, reutilizadas sem criar dossier sintético em produção. Conferência visual de navegador permanece separada; não se presume instalação de outros módulos, fecho integral do portal ou conclusão dos resíduos fiscais. Nenhuma nova instalação/alteração de direitos exigida para declarar este resultado. Prova `outputs/funding-directory-20261110/institutions/publication-complete.json`.
## 10/11/2026 — recusa depois do login: navegadores e sessões diferentes
URL comunicado pelo utilizador:`/dashboard`. Conferência dirigida dos registos confirma login/MFA no **Edge** às22:03:53, seguido de`/dashboard`HTTP200 às22:03:55. A abertura seguinte de`/management/fundos/referencias-assistidas`às22:04:02/22:04:17 recebeu403 em **Safari/WebKit**, não no Edge. Existiam duas sessões da mesma conta: a sessão recente do Edge com confirmaçãoMFA válida e a anterior com confirmação antiga. A autorização editorial/verificação de email/perfil existentes estavam preservados. Logo esta nova recusa não comprova expiração do MFA recém-concluído no Edge nem falta de permissão da conta; o link abriu outro contexto de navegador.
Solução indicada: copiar e colar o URL do editor na barra do Edge já autenticado, mantendo a mesma aba nas retomas. Não repetir login por rotina, alterar permissões, desactivar verificações ou reinstalar por este motivo. **Reposição de acesso confirmada:** GETdo editor noEdge às22:11:21(+02),HTTP200. A recusa de entrada ficou delimitada e resolvida por uso da sessão correcta, sem nova instalação, concessão ou alteração de segurança. A instalação4f3dbcf8 permanece; publicação/conteúdo consultável ainda não declarados concluídos. Consulta estritamente de leitura; nenhum identificador de sessão/token/cookie/palavra-passe exportado e nenhuma sessão modificada. Prova:`outputs/funding-directory-20261110/institutions/access-context-diagnosis.json`.
## 10/11/2026 — retoma editorial instalada em4f3dbcf8
Correcção instalada pelo executor Git existente em `4f3dbcf86499180011fc79712d6b5be445e57e26`, confirmada às22:01:41Africa/Johannesburg (20:01:41UTC).7ficheiros conferidos por hash; revisão/entrada/login/up/Concursos/fundos HTTP200, configuração preservada, zero migrações/importações automáticas. Novo backup/restauro190tabelas/369ficheiros conferido, sem substituir dados activos. Pacote `outputs/entrega-instituicoes-retoma-20261110.zip`, SHA256 `9bce0250d546cd87f7c60a55f0c1d47fba74e8965f7d3224a588e140f87ec3cb`; anteriores preservados.
**Saldo activo:90rascunhos, zero publicações.** A indicação de publicação não foi confirmada no servidor por expiraçãoMFA/403. A correcção torna visível o estado e conserva os recibos na mesma aba, poractor/fontes; não publica por consequência da instalação nem altera o limiteMFA900s. Os90rascunhos anteriores ao armazenamento no cliente são recuperados idempotentemente na primeira passagem actualizada; após isso as fichas conferidas são saltadas nas retomas da mesma aba. Confirmações de revisão/publicação voltam a ser exigidas na entrada, e o backend revalida guardas/hash/versão. Fechar a aba/limpar armazenamento não elimina rascunhos, mas perde a retoma local. Estado alterado por outra janela exige conferência actual; o armazenamento do cliente não é prova de publicação.
Resta a conclusão autenticada da preparação/publicação e a consulta efectiva do conteúdo no destino. ACT12 continua aberto, visual pendente, sem reabrir APIEX/SWOT/S08 ou resíduos fiscais anteriores. Prova: `outputs/funding-directory-20261110/institutions/resume-installed.json`.
## 10/11/2026 — retoma após MFA, sem repetir fichas conferidas
Verificação activa posterior:90rascunhos/zero publicações; continuação GETMomentum403 às21:49:39 ePOSTpublicar403 às21:49:54 (+02). A sessão voltou a expirar. A publicação comunicada não chegou a ser executada pelo servidor. Nenhum rascunho foi removido.
Correcção delimitada emQA: a mesma aba conserva selecção, recibos/versionamento/hash e fundamento, separados por conta e conjunto de fontes; após reautenticação salta fichas já conferidas. As confirmações de revisão/publicação são desmarcadas na nova entrada, sem publicação automática. Uma publicação de resposta incerta conserva o mesmo corpo para recuperação idempotente. Novo comando de renovação utiliza logout/login/MFA nativos; limite900s e guardas mantidos. «Seleccionar todas as instituições(270)», contagem de progresso e controlos antes da lista recolhida tornam o estado visível. Sem migração, dados novos ou alterações de permissões. Alteração ainda não instalada nesta anotação; revisão activaef24cbdc.
Prova dirigida do componente efectivo: remonta a página como após autenticação, recupera selecção/recibos, não repete preparação, não publica automaticamente, recupera confirmação incerta e isola outra conta/fontes. Prova API editorial anterior1caso/27asserções reexecutada apenas pela propriedadeeditor_id acrescentada; provas270fichas e do dossier reutilizadas. Tipos/análise/compilação da entrega são registados pelo executor existente.
## 10/11/2026 — confirmação da preparação parcial e expiração do MFA
Após execução no editor, a homologação contém **32rascunhos e zero fichas publicadas**. A preparação gravou efectivamente e avançou por diferentes instituições. Os registos HTTP mostram preparação200 atéMyBucks às21:31:42, seguida deGETxtenda403 às21:31:43 ePOSTpublicar403 às21:31:59 (Africa/Johannesburg). A entrada no editor fora confirmada às21:16:43; corresponde ao limite existente de900segundos do MFA em `PortalProfileAccess::mfa`. Não é perda da instalação nem repetição de dados. Reautenticar no portal com MFA e retomar a preparação idempotente conserva os32rascunhos, sem substituir edições; publicação continua a exigir a confirmação separada. Nenhuma guarda, sessão ou permissão foi alterada. Revisão instalada `ef24cbdc`, ACT12 ainda sem conteúdo público confirmado. Prova de leitura: `outputs/funding-directory-20261110/institutions/publication-observation.json`.
## 10/11/2026 — instalação confirmada do directório financeiro
Revisão `ef24cbdc9218e2027f7d1a39968cc60869a35410` instalada na homologação às19:15UTC, pelo executor Git existente.33ficheiros conferidos por hash com o commit; configuração preservada, zero migrações/importações automáticas. Backup/restauro confirmado de190tabelas e369ficheiros. Entradas `/fundos?kind=institution`, `/concursos`, `/login`, `/up` e revisão HTTP200. Pacote anterior preservado; nova entrega `outputs/entrega-instituicoes-editorial-20261110.zip` SHA256 `168d7ffb6d054533674f3f07799959b46bc12641c045accfc1766da5cead9077`.
**Implementação instalada; conteúdo ainda sem publicação confirmada.** Leitura activa:zero fichas/zero publicações; tabelas sem RLS, logo o resultado não decorre de falta de contexto de leitura. Entrada autenticada no editor assistido confirmada nos registos; ainda sem pedidos POST de preparação/publicação observados. A indicação do utilizador de operação concluída permanece por reconciliar com o resultado efectivo, sem repetir a importação ou simular MFA. A conta existente tem designação editorial e perfil exigidos; nenhuma concessão alterada. ACT-20261029-12 permanece aberto até existirem fichas consultáveis. Visual e restantes resíduos do portal mantidos.
## 10/11/2026 — preparação editorial assistida das instituições
A autenticação SSH foi confirmada, mas não substitui a sessão e MFA recentes do editor no portal. Acrescentada `/management/fundos/referencias-assistidas`: selecção explícita das instituições do documento, conferência das fontes, preparação sequencial idempotente e publicação confirmada numa segunda operação. Preparar nunca publica. Fichas deliberadamente alteradas são preservadas e exigem revisão individual; mudança do conjunto de fontes interrompe a operação. Uma resposta de publicação perdida conserva versão/hash/fundamento para a mesma repetição. Expiração de MFA mantém as guardas e os resultados já confirmados.
Prova adicional delimitada:1caso API/27asserções,1caso frontend de separação preparação/publicação e recuperação da confirmação. Reutilizadas as5provas API/735asserções e28frontend anteriores; não se repetiu a carga das270instituições. Instalação e publicação no destino continuam distintas e serão registadas após confirmação efectiva. Sem migrações, novos direitos ou alteração das sessões existentes.
## 10/11/2026 — instituições por localização, categoria e tipo no processo do investidor
Pedido expresso da Tempo: inserir as instituições do documento agora indicado por localização/categoria/tipo e permitir que o investidor consulte bancos, seguradoras e outros intervenientes quando a necessidade surgir, sem limitar a decisão por falta de informação. **Implementação e conteúdo demonstrados em QA; nenhuma instalação ou publicação activa executada nesta passagem. Última revisão instalada confirmada:1c75a30a, APIEX/SWOT/Concursos preservados. ACT-12 não é encerrado pela preparação técnica.**
Original read-only `Notas/Instituições financeiras em Moçambique.docx`, edição Novembro2026, SHA256 `119bb5514d2777849b70aebfec21d28a41885cbdc7d9827fbebd764fe994c108`. Texto,509linhas de tabelas e nomes/contactos presentes nos parágrafos reconciliados. **270instituições/operadores,393referências de localização física e168referências de contacto**, não270ofertas de financiamento nem393moradas independentemente confirmadas. Instituições repetidas são reunidas por identidade explícita/aliases documentados ou número de registo citado; divergências, moradas alternativas, terceiros, dados históricos e identidades por confirmar permanecem identificados, sem substituir factos. Entidades sem morada continuam consultáveis como não localizadas. Totais estatísticos/redes incompletas não criam entidades ou agências fictícias. Listas antigas não confirmam licença actual; ABC permanece fundida/extinta segundo o documento, ligada à referência Access, sem atribuir a origem dos balcões por inferência.
Reutilizado o catálogo/versionamento/editorial existente: categorias banca,crédito/microfinanças,gestão de investimento,pensões,seguros/resseguros,mediação,câmbio,pagamentos e supervisão; uma entidade pode ter mais de uma categoria/função sem duplicação. Filtros por categoria,tipo e presença física são distintos da cobertura do produto. Província do cadastro é associada apenas por correspondência inequívoca; rua/localidade e precisão limitada conservam-se como declaradas. Pesquisa por cidade/balcão/morada/alias permite localizar informação textual (por exemplo Beira), sem geocodificação inventada. Informação por confirmar não é correspondência confirmada; ausência de registo não prova ausência de oferta. Oito gestoras de pensões continuam distintas de produtos de crédito, e seguradora não é apresentada automaticamente como financiadora/garantia de crédito.
**Continuidade no mesmo dossier:** ferramentas do investimento abrem a pesquisa contextual; seleccionam-se necessidade (incluindo seguros), instituição e, opcionalmente, sede/balcão. Guarda-se identidade estável, categorias/tipos, versão pública, local exacto escolhido, fonte/documento/SHA/linha, estado de confirmação e produto quando existente. Banco e seguradora coexistem; gravação repetida não duplica escolha. Actualização posterior dos pressupostos do investimento conserva a escolha; retirada da ficha pública não apaga a fotografia consultada. Dados/etapas/autoridade de decisão anteriores não são alterados. Consulta pública livre, operações privadas e edição editorial com as guardas existentes; nenhuma candidatura, email ou contacto enviado às instituições, novo perfil/grant/porta/schema ou ligação fictícia.
Provas no QA55441 existente, com rollback: **5casos API/735asserções únicas** (2desta fonte/608 e3de compatibilidade/127),28frontend dirigidos incluindo S08/navegação e recuperação da tentativa com balcão escolhido, tipos/PHPStan/lint/Pint/compilação QA aprovados. Uma tentativa de suite paralela foi recusada pela guarda da mesma base; compatibilidade executada sequencialmente sem remover a guarda ou criar outra base. `outputs/funding-directory-20261110/institutions/{content,verification}.json` e **`catalogo-qa.html` com270fichas ligadas**; renderização estática dos componentes, não conferência visual de navegador. Provas e pacote anteriores das11fichas/BNI Capital/FDEL mantidos. As7instituições comuns são enriquecidas pela mesma identidade; não há segundo directório. Fontes/documento original não foram alterados.
**Saldo concreto:** conteúdo e percurso local/categoria/tipo → banco/seguradora → fonte/contacto → mesmo dossier → continuação/recuperação demonstrados em QA. Restam instalação e disponibilização/revisão/publicação autenticada pelo fluxo editorial existente, consulta efectiva no destino e visual (conector indisponível). Preparação documental não publica lote automaticamente, conformeAGENTS.md; não declarar informação já disponível na homologação por existir este código. Restantes módulos e resíduosL1–L7/restanteL8/L9–L16 mantêm o saldo anterior.
## 10/11/2026 — ACT-20261029-12: directório financeiro com conteúdo em QA
**Homologação mantém1c75a30a; este conjunto não está instalado nem publicado.** A leitura autenticada do destino confirmou catálogo /fundos HTTP200, schema instalado e zero fichas/versões. O requisito não fica encerrado por essa disponibilidade técnica. APIEX/SWOT mantêm a entrega instalada própria, sem reabertura.
Localizada e reutilizada a lista fornecida em `Relatorios/benchmark de melhorias investeMoz.docx`, SHA256 `065203aacfae766c62cf9c2c272ebacc61ff23bd4ff9e91535939fede87f7617`, quadro de04/10/2026. Doze entradas documentais correspondem a **11fichas:7instituições e4programas/fundos** — BCI,Millennium bim,Absa,Standard Bank,Moza,BNI,Gapi,FECOP,Semeia Negócios,FCID VIII eFAE; **BNI Capital** integra os produtos do BNI, sem duplicar instituição ou presumir personalidade jurídica autónoma. Agregado genérico de linhas BNI não gera fundos fictícios. Referência FDEL anterior conservada, sem a contar novamente nesta entrega.
O cadastro/versionamento/competência editorial existentes passam a conservar natureza factual, identidade estável, contactos com fonte/data, locais físicos (sede/balcão/ponto institucional) e produtos próprios. Moradas não determinam cobertura financeira. Produto tem finalidade, instrumento, condições/montantes, disponibilidade e cobertura próprios; vazios são identificados como por confirmar. Cobertura e necessidade têm de corresponder ao **mesmo produto**, antes da paginaçãoSQL. Critério documentado pode excluir desconhecidos; consulta geral distingue resultados por confirmar de correspondência sustentada. FCID conserva seis províncias documentadas, sem confundir Maputo Província/Cidade; FAE mantém o Vale do Zambeze como âmbito textual com vínculo geográfico por confirmar, sem inventar distritos. A localização institucional do BNI não limita o financiamento a Maputo. Semeia conserva a chamada encerrada; a antiga linha BCI2018 não é reaberta. Condições e janelas históricas não se tornam ofertas actuais pela importação. Fontes indicadas consultadas para âmbito/disponibilidade; falha de recolha da Gapi não revoga o original, cuja data04/10 e disponibilidade por confirmar ficam explícitas.
Demonstrado em QA55441 existente: localização/necessidade → consulta das fichas reais fornecidas → fonte/contacto/condições → escolha privada no mesmo dossier e oportunidade → recuperação. Snapshot preserva versão pública e produto consultados mesmo após retirada/alteração da ficha. Repetição da intenção e recuperação após recarregar não duplicam a escolha/evento; outra oportunidade/actor e dossier encerrado mantêm guardas. Não envia candidatura, contactos ou dados às instituições, não concede crédito nem classifica o investidor automaticamente. Sem pontuação/IA, schema/base/porta nova, novas competências ou alteração de dados activos.
Provas dirigidas: **3casos API/127asserções e13frontend**, PHPStan nível7 sem supressões, tipos/lint/Pint/compilação QA aprovados. Testes transaccionais revertidos; sequências QA podem avançar. `outputs/funding-directory-20261110/verification.json`, `content-qa.json`, `dossier-qa.json`; **catálogo consultável de revisão** `outputs/funding-directory-20261110/catalogo-qa.html` e11fichasHTML ligadas. Renderização estática dos componentes afectados, não conferência visual de navegador nem publicação no destino. Provas anteriores preservadas e reutilizadas.
**Saldo ACT-12:** recorte funcional e conteúdo fornecido demonstrados em QA, preparado para disponibilização pelo fluxo editorial. **Restam instalação do conjunto, preparação/revisão/publicação autenticada das11fichas no destino e confirmação da consulta real pelo investidor**; visual continua pendente com conector indisponível. Não declarar ACT-12 encerrado ou o portal integral. Dados de sede/balcões, condições ou cobertura não documentados conservam o estado por confirmar; não bloqueiam os restantes dados fundamentados. Resíduos L1–L7/restanteL8/L9–L16 e outros módulos mantêm o saldo anterior.
## 10/11/2026 — APIEX, SWOT e acesso destacado a Concursos instalados
**Homologação instalada e conferida: `1c75a30a601ef00fc187ddf5a64183446d329e6f`**, https://teste-investidor.tempo.co.mz. Inclui APIEX/SWOT37854d02 e correcção do menu autorizada pela Tempo. **Concursos e anúncios** tem agora grupo próprio no menu horizontal comum, com consulta pública e acompanhamentos para utilizadores autenticados; endereço https://teste-investidor.tempo.co.mz/concursos. O módulo já estava activo e apenas a entrada estava dentro de Descoberta; não foi necessário activá-lo novamente. Um rascunho privado e zero versões publicadas preservados.
Conferência final:21hashes Git iguais à release, revisão HTTPS correcta e root/up/login/concursos HTTP200, configuração intacta, zero migrations/importações. Backup fresco restaurado com190tabelas/contagens/checksums iguais e369ficheiros/configuração conferidos;7476dc34 e pacotes anteriores preservados para reversão de código. Recibos `outputs/apiex-swot-concursos-delivery-20261110/{backup,restore,files-restore,verification,delivery}.json`; verificação UTC no recibo. Instalação executada pelo mesmo executor Git, sem alterações de permissões, dados reais, publicação de análises ou submissão externa.
**Saldo:** APIEX e SWOT abaixo deixam de ser pendência de instalação. Acesso pelas ferramentas **Preparação APIEX e SWOT** do dossier existente, conservando a oportunidade seleccionada. Conferência visual no uso normal continua pendente no mesmo registo; não é inferida das provas automáticas. Âmbitos normativos não mapeados continuam indeterminados; os restantes módulos e resíduos L1–L7/restanteL8/L9–L16 conservam o saldo anterior. O portal não é declarado integralmente concluído.
## 10/11/2026 — correcção dirigida das âncoras antes da promoção
O executor recusou c7901592 por um caso frontendS08 (1falha/1788), mantendo7476dc34 activo. Diagnóstico delimitado: a página tinha dois efeitos de âncora e o teste existente conservava uma única sequência de dependências. A abertura SWOT foi integrada no listener de hash já existente, também para mudanças posteriores de #tarefas, sem alterar a guarda/foco/expansão de S08. O teste S08 aceite conserva as suas asserções; acrescentado caso que alterna #tarefas/#S08, verifica isolamento no dossier e limpeza do listener.15casos dirigidos APIEX/SWOT/âncoras aprovados. Nenhuma alteração de dados, permissões, fontes normativas ou configuração; entrega autorizada retomada no mesmo mecanismo, sem mascarar a falha nem desactivar verificações. Recibos finais continuam a determinar a revisão instalada.
## 10/11/2026 — Concursos visíveis no menu principal; instalação agrupada autorizada
Conferência delimitada do destino7476dc34 por SSH autenticado: `procurement.enabled=true`, módulo disponível, tabelas presentes; `/concursos` e raiz HTTP200 com `procurementEnabled=true`. Preservados um concurso em rascunho e zero versões publicadas. O módulo não desapareceu nem estava desligado: a ligação estava dentro de Descoberta. Pedido Tempo «onde foram parar concurso? depois de resolver isso autorizo a instalação»: entrada promovida a grupo próprio **Concursos e anúncios** no menu horizontal comum, com **Consultar concursos e anúncios** e, após autenticação, **Os concursos que acompanho**. Retirada só a ligação duplicada de Descoberta; cinco atalhos principais e autorizações editoriais preservados. Não cria/publica concursos nem muda flags, dados ou schema.
Prova dirigida da navegação:20casos aprovados, incluindo visitante/utilizador, disponibilidade, destinos únicos e selecção correcta de catálogo/ficha/acompanhamentos. Tipos/lint/compilação QA aprovados. Observação anterior `outputs/investment-preparation-20261110/concursos-before.json`; pacote agrupado `outputs/entrega-apiex-swot-concursos-20261110.zip`, preservando o pacote APIEX/SWOT anterior. O conjunto APIEX/SWOT abaixo conserva as provas aceites, sem repetição. Instalação autorizada expressamente após esta correcção; execução e recibo final separados desta prova QA. Visual continua pendente por indisponibilidade do conector.
## 10/11/2026 — APIEX e SWOT no dossier existente: QA concluído, instalação pendente
**Instalado:** `7476dc343098f15a7982e9d7581bf4f59d43bc1a` em https://teste-investidor.tempo.co.mz, promovido às14:28:12UTC e conferido às14:29:07UTC (16:29 locais). Transporte CBF15 deixa de ser pendência de instalação. Delta14hashes Git/HTTPS, root/up/login/revision200, configuração intacta, zero migrations/importações; backup/restauro190tabelas e369ficheiros conferidos. Recibos `outputs/cbf15-carry-delivery-20261110/`; release anterior e pacotes preservados.
**Implementado em QA, ainda não instalado — APIEX:** dossier existente → requisitos fundamentados disponíveis/em falta/não aplicáveis/indeterminados → documentos existentes e complementos → revisão → prévia/ZIP com índice e anexos íntegros → registo da entrega externa, esclarecimento, resposta e decisão com data/referência/comprovativo. Dados do investidor/empresa/contexto/localização/oportunidade reutilizados. Estados em preparação/pronto para entrega/entrega registada/decisão recebida distintos; exportar não submete nem aprova. Snapshot preparado, versões, SHA e pacote privado cifrado preservados; recuperação devolve os mesmos bytes após alteração do contexto, revalidando acesso e segurança dos documentos. Ausência de recepção electrónica APIEX, segundo a orientação Tempo, não bloqueia este percurso; nenhum conector fictício ou formulário oficial inventado. Reutiliza o avaliador e modelos/fontes aceites; períodos/regimes fora do âmbito actualmente mapeado ficam indeterminados, sem conclusão normativa presumida.
**Implementado em QA, ainda não instalado — SWOT:** no mesmo dossier/oportunidade, objecto explícito investimento/território/sector/cluster, evidências do contexto/análise proactiva e Competitividade pública aprovada, quatro dimensões internas/externas, fonte/data/classificação de cada conclusão (facto documentado/hipótese/por confirmar). Sem pontuação, IA ou inferência de ausência pela falta de registos. Gravação/recuperação/histórico paginado e revisão pela equipa com permissões existentes. Alteração/retirada de fontes assinala revisão, conserva fotografia consultada e edições deliberadas. Acções ideia/oportunidade/parceiros/risco continuam no mesmo dossier; risco liga à tarefa nativa com origem própria e repetição sem duplicação. Privado por defeito; publicação exige o fluxo editorial existente, nunca automática. Contextos retirados ficam no histórico privado de leitura, sem reactivação.
Provas sintéticas no QA55441 existente, com rollback: seis casos API/211asserções;16casos frontend dirigidos (incluindo transporte e cabeçalho comum147páginas), tipos, análise estática e lint aprovados. `outputs/investment-preparation-20261110/verification.json`, ZIP/índice/anexos, recuperação SWOT e provas de isolamento/histórico/segurança. Pacote Git agrupado `outputs/entrega-apiex-swot-20261110.zip`, base exacta7476dc34, reversão de código sem apagar auditoria, pacotes privados ou tarefas posteriores. Nenhuma migration/base/porta/conta real nova, submissão real ou publicação. Compilação QA separada; provas anteriores reutilizadas.
**Saldo concreto:** APIEX e SWOT acima encerrados em implementação QA; instalação deste conjunto e conferência visual continuam pendentes. O conector mantém “The browser access host is unavailable”; SSR/testes não substituem visual, sem novo ciclo de reinícios. Os restantes módulos conservam o saldo anterior, incluindo resíduos L1–L7, restanteL8 e L9–L16. Isto não declara o portal integralmente concluído.
## 10/11/2026 — transporte documental CBF15 em QA
Homologação mantém `d216830b`; este delta ainda não está instalado. Tipos/lint/Pint/buildQA aprovados; pacote com aplicação/reversão exactas,sem migrations/dados/credenciais. Pendência encerrada no recorte normal residente2026/ano civil/MZN: crédito de investimento de origem anterior do mesmo contribuinte/projecto/local/regime, com activo/documento/parcela, exercício de origem, NUIT/projecto de origem, taxa original e qualificação históricas documentados. Exercício de origem anterior a2026,dentro da janela original e não anterior à aquisição; no projecto em funcionamento,a aquisição não precede o início aplicável. Não renova o prazo nem recalcula origem a outra taxa. Valor corrente/ausência e uso efectivo agregado exigem reconciliação explícita; histórico de simulações não equivale a utilização fiscal.
Cálculo → POST/PUT do mesmo rascunho privado → GET → recálculo → HTML/dossier demonstrados no QA55441 existente com rollback. Exemplo sintético recuperado: nominal de origem4.000,uso efectivo anterior1.000,disponível3.000,quota documental do projecto200 → dedução200,não utilizado2.800,saldo500MZN. Início2022 → quinto exercício2026; mudando o início para2021 → sexto,parcela expirada3.000,disponível/dedução0,saldo700,com activo,uso anterior e origens preservados. Seis/sete resultados históricos conservados,incluindo o resultado ordinário aceite. Provas `outputs/irpc-cbf15-carry-20261110/{carry,expired}-verification.json` e relatórios respectivos; API um caso/28asserções novas,30casos frontend nas fronteiras alteradas incluindo formulário real compilado e prevenção de duplicação. SSR/formulário simulado não é navegador/visual. FonteCBF15(4/7),APIEX2025 PDF71–72 já aceite,reutilizada.
A preparação mantém os activos correntes e exige explicitar os dados em falta; activos de origem e correntes partilham a capacidade documental e a detecção de duplicação entre mapas. Origem com outro NUIT/projecto/taxa,exercício incoerente,uso superior ao nominal ou declaração de ausência incompatível não produz dedução. Sem pagamento,reembolso,consumo oficial ou criação automática de saldo transportado. Livros antigos permanecem nos formatos1/2; só o transporte preparado usa livro3/detalhe2,para o código anterior recusar em vez de ignorar o crédito novo. Snapshots e históricos aceites não são reescritos; reversão é de código e não deve sobrescrever dados posteriores.
Saldo preservado: outros projectos/localizações/regimes/cumulações/EP/L7,combinação positiva com transportes económicos/internacionais/PEC anteriores,repartição automática,benefícios/formulários oficiais e restantes resíduos L1–L7/restanteL8/L9–L16 mantêm os seus recortes. Os módulos não fiscais mantêm o saldo existente,sem nova auditoria geral. Próximo trabalho: primeira combinação residual executável respeitando a ordem de dedução e quota do projecto,ou percurso independente do saldo existente; visual continua pendente no mesmo registo. Não é conclusão integral do portal.
## 10/11/2026 — menus horizontais instalados
**Homologação: `d216830b068af8a7a40d43993795639e73aaed78`**, promovida às13:56:27UTC e conferida às13:56:54UTC (15:56 locais), em https://teste-investidor.tempo.co.mz. Delta10hashes Git e revisão HTTPS iguais; root/up/login/revision HTTP200, configuração preservada, zero migrations/importações. Backup/restauro190tabelas com contagens/checksums iguais e369ficheiros/configuração conferidos; revisão d5cb41b1 preservada para reversão. Recibos `outputs/horizontal-menu-delivery-20261110/{backup,restore,files-restore,verification,delivery}.json`. Pacote anterior e dados/históricos preservados; sessãoSSH pronta para continuação autenticada, sem credencial em disco.
O recorte dos menus horizontais abaixo deixa de ser pendência de instalação: cabeçalho comum em páginas públicas/autenticação/definições/privadas, sem sidebar global concorrente; Descoberta sem repetição dos cinco atalhos mantidos na entrada principal. Prova estrutural dos146layouts e23casos dirigidos reutilizada; não é conferência visual. O conector continua indisponível,sem novos reinícios ou alterações ao navegador. Visual mantém-se pendente no mesmo registo. O restante saldo de funcionalidades por módulo e L1–L7/restanteL8/L9–L16 não é encerrado por esta instalação.
## 10/11/2026 — janela CBF15 instalada; menus horizontais uniformizados em QA
**Homologação: `d5cb41b111cac95c431a3072b8c4cc0141481ec2`**, promovida às13:43:40UTC e conferida às13:44:18UTC (15:44 locais). Delta12hashes Git e revisão HTTPS iguais; root/up/login/revision HTTP200, configuração preservada, zero migrations/importações. Backup/restauro190tabelas e369ficheiros/configuração conferidos,89e6d72c preservada. Recibos `outputs/cbf15-window-delivery-20261110/`. Janela de cinco exercícios CBF15 deixa de ser pendência de instalação; transporte de créditos anteriores e restantes resíduos fiscais continuam separados.
**Delta posterior em QA, ainda não instalado — pedido expresso da Tempo10/11, corrigido para menus horizontais com dropdown:** cabeçalho comum passa a ser montado uma vez pelo layout global em todas as páginas; área privada usa o mesmo cabeçalho sem navegação lateral concorrente. Autenticação, definições, gestão e páginas públicas conservam conteúdo e breadcrumbs; filtros de capacidades, direitos e destinos privados existentes preservados. Esconder cabeçalho na impressão mantém-se. Os cinco atalhos (Porquê investir,Partir de uma ideia,Consultar oportunidades,Simulador Fiscal,Sectores) ficam intactos na entrada principal. Descoberta conserva localizações/mapa,catálogos e módulos opcionais,sem repetir os atalhos; Partir de uma ideia já estava fora desse grupo. CSS responsivo e implementação comum dos dropdowns/teclado/Escape/toque reutilizados,sem novo menu alternativo.
Conferência estrutural dos layouts das146páginasTSX existentes: exactamente um cabeçalho comum por composição, incluindo páginas privadas,autenticação/definições, sem sidebar global; conteúdo/contexto/breadcrumbs e cinco atalhos preservados. Provas `tests/frontend/portal-horizontal-shell.test.mjs`, navegação e renderização dirigida (23casos aprovados). É SSR com dependências de UI substituídas,não prova visual de navegador. Tentativa única de inventárioCUA nesta continuação devolveu exactamente `Browsers: Error: The browser access host is unavailable`; nenhuma alteração/reinício de navegador. Conferência visual mantém-se pendente no registo existente. Tipos/lint/build QA aprovados, logs desta entrega; base activa/dados/permissões intocados.
O saldo integral dos restantes módulos e L1–L7/restanteL8/L9–L16 permanece no plano existente; nenhuma contagem de páginas/testes declara o portal concluído.
## 10/11/2026 — janela de cinco exercícios CBF15 em QA
Homologação mantém `89e6d72c`; o delta seguinte ainda não está instalado. Pendência encerrada em QA: contagem inclusiva dos cinco exercícios civis do CBF15(4/7) na liquidação2026, para investimento efectivo do próprio2026. Início de exploração para projecto novo; início dos procedimentos após aprovação para projecto em funcionamento. Não usa cinco anos móveis nem reinicia o prazo pela data de aquisição. Início2022, mesmo em31/12 → quinto exercício2026, dedução200 pela quota residual, saldo500MZN. Início2021 → sexto exercício2026, prazo expirado, dedução0 e saldo700; activo e documento preservados. Datas inválidas/futuras e activos antigos apresentados como investimento corrente permanecem indeterminados/recusados, não zero por omissão.
Cálculo → POST/PUT do mesmo rascunho privado → GET → recálculo → HTML/dossier demonstrados no QA55441 existente, com rollback, cinco/seis resultados históricos e todos os anteriores preservados. Provas `outputs/irpc-cbf15-window-20261110/{fifth,expired}-verification.json` e relatórios respectivos. Modelo `IRPC-CBF15/2`, livro existente versão2 sem migration; snapshots aceites não são reescritos. Fonte já aceite APIEX2025 PDF71–72,CBF15(4/7), reutilizada sem nova validação de autenticidade. Provas anteriores reutilizadas; apenas fronteiras alteradas conferidas.
Saldo: transporte de créditos de investimento de anos anteriores, repartição automática/múltiplos projectos, EP/L7/cumulações/outros benefícios/formulários oficiais continuam por implementar nos respectivos recortes. A janela corrente não declara o transporte entregue. Resíduos L1–L7, restanteL8 e L9–L16 e saldo dos módulos não fiscais preservados. Conferência visual pendente no mesmo registo; portal não integral. Próximo trabalho executável: transporte documental CBF15 dentro da mesma janela, com consumo anterior reconciliado e sem duplicar o investimento corrente.
## 10/11/2026 — CBF15 instalado na homologação
**Revisão instalada: `89e6d72c1e86df8eb86e7be74583659975585036`**, promovida às13:27:59UTC e conferida às13:28:21UTC (15:28 locais), em https://teste-investidor.tempo.co.mz. Os17hashes do delta coincidem com Git; revisão/root/up/login HTTP200, configuração preservada, zero migrations/importações. Backup fresco/restauro190tabelas com contagens/checksums iguais e369ficheiros/configuração extraídos e conferidos;7c8d4726 preservada para reversão. Recibos `outputs/cbf15-delivery-20261110/{backup,restore,files-restore,verification,delivery}.json`; pacote imutável `outputs/entrega-cbf15-20261110.zip`, SHA25613bdecef2697edcc821d6fa490586d0c2f4b171b4308c655aa60ba5632664c83.
O recorte CBF15 abaixo deixa de ser pendência de instalação: primeiro exercício2026, residente geral/MZN, activos e condições documentados, colecta integral do projecto ou quota residual documentada, cálculo/gravação/recuperação/relatórios com histórico. As notas QA anteriores ficam como histórico das provas aceites. Outros exercícios/transporte/caducidade, repartição automática/múltiplos projectos, EP/L7/cumulações/outros benefícios/formulários e restantes resíduos L1–L7/L8/L9–L16 permanecem no plano existente. Próximo trabalho: residual executável de CBF15/L8, preservando o recorte instalado e sem repetir as provas aceites. Conferência visual continua pendente no mesmo registo; portal não declarado integral.
## 10/11/2026 — acordo PT–MZ instalado; crédito CBF15 demonstrado em QA
**Homologação: `7c8d4726e9f93325480e2ee6e5e790e9adbcaf19`**, promovida às12:36:07UTC e conferida às12:37:11UTC (14:37 locais). Revisão HTTPS/10hashes iguais, quatro entradasHTTP200; configuração preservada, zero migrations/importações. Backup/restauro190tabelas e369ficheiros/configuração conferidos, b10b35f3 preservada. Recibos `outputs/cdt-agreement-delivery-20261110/`. Falha inicial de recepção Git delimitada ao directório objects/7c pertencente aroot: apenas proprietário/grupo ajustados a conta do serviço,modo0755 inalterado; pedido original validado recolocado sem alterar corpo/SHA ou controlos. Recibos git-permission-repair/request-retry; não foi falha do portal nem alteração de segurança do navegador. Sessão SSH preservada para continuação autenticada, sem credencial em disco.
**Pendência efectivamente encerrada em QA, ainda sem instalação:** CBF15, crédito fiscal por investimento efectivo no primeiro exercício2026, residente geral/ano civil/MZN, colecta inteiramente da actividade do projecto ou quota residual documentada após créditos anteriores e sem outros benefícios próprios/cumulações ou transportes positivos combinados. Reutiliza qualificação CBF de L7 e fonte APIEX já aceite (recibo irpc-l7-common-qa-20261026,compilação oficial2025,PDF66–72,art15PDF71–72), não importação como activação. Localização do investimento,aprovação/condiçõesCBF3/9/10/regime genérico13,início aplicável e activos/documentos exigem correspondência própria. Activo novo corpóreo,nacional,produtivo e fora15(6); condições negativas/exclusões documentadas produzem crédito zero nessa linha,preservando os dados. Informação em falta não é zero.
Percurso completo: preparar detalhe → calcular após económico/internacional e antesPEC/RF/PPC → gravar/actualizar pelo writer privado existente → recuperar → recalcular → HTML/dossier com fornecedor/documento/parcela/valores/qualificação/período/histórico. Maputo cidade5%: investimento10.000 → crédito500 → saldo200 MZN. Restantes províncias10%: crédito1.000 → saldo−300, recuperável simulado devido aoPPC. Cenário de limite: nominal4.000,dedução1.400,crédito não usado2.600,PEC não utilizado200,PPC500; nenhum reembolso de crédito/PEC ou saldo oficial criado/consumido. Provas `outputs/irpc-cbf15-20261110/{city,province,capped,partial}-verification.json` e relatórios respectivos. Quatro versões do mesmo rascunho API em transacção revertida QA55441,com histórico anterior e versões seguintes preservados; zero alterações activas.
Quota residual documentada: nominal4.000, limite do projecto200, dedução200, não utilizado3.800 e saldo500 MZN; recuperada pela API com quatro resultados históricos. Quota superior à colecta global remanescente, negativa ou sem fundamento é recusada. Não há repartição automática de rendimentos/custos/perdas. Tipos, lint/Pint e compilação QA aprovados; provas anteriores reutilizadas.
Livros anteriores L8 permanecem versão1,com chaves/resultados inalterados quando sem CBF15. Só o livro com este novo detalhe usa versão2: cliente anterior recusa o formato,conservando o snapshot,em vez de recalcular ignorando o novo benefício. Não é migration de base. O resumo antigo dos benefícios fica conservado e não reaplicado; ausência dos restantes benefícios exige declaração/reconciliação explícita,mesmo quando CBF15 é ausente. Fonte documental tem capacidade por factura/parcela e entre mapas da entidade; mudar id/data não duplica investimento. Repetir simulação não é uso fiscal anterior.
**Saldo preservado:** CBF15 de outros exercícios/transporte/caducidade,repartição automática/múltiplos projectos,EP/L7/cumulações,outros benefícios e obrigações/formulários oficiais continuam separados. Ausência/zeroCBF15 não bloqueia os saldos anteriores aceites. Restantes L1–L7/L8/L9–L16 e módulos não fiscais mantêm o saldo existente; o portal não está integral. Visual permanece pendente. Conjunto CBF15 preparado para entrega sobre7c8d4726,sem instalação nesta entrada.
## 10/11/2026 — b10b35f3 instalado; acordo institucional PT–MZ em QA
**Homologação: `b10b35f30830922dae00afde50ac2bdb0e8cef6f`**, promovida às12:15:13UTC e conferida às12:16:31UTC (14:16 locais). Revisão HTTPS e16hashes Git iguais, quatro entradasHTTP200; configuração preservada, zero migrations/importações. Backup/restauro190tabelas com contagens/checksums iguais e369ficheiros/configuração conferidos;60b52fc8 disponível para reversão. Recibos `outputs/government-fraction-delivery-20261110/{backup,restore,files-restore,verification}.json`. Sessão SSH mantida aberta para continuação autenticada, sem credencial em disco. Isenção por devedor público11(3)a e fracções económicas L6 deixam de ser pendências de instalação; as anotações anteriores QA são históricas nesses recortes.
**Delta posterior QA, ainda não instalado:** correspondência11(3)b para instituição/organismo beneficiário corrente que concedeu o financiamento no âmbito de acordo concluído entre os governos PT–MZ. Beneficiário, financiamento ligado aos juros e acordo/cláusula aplicável exigem suporte próprio. País, nome da entidade ou qualificação institucional isolada não activam isenção. Fundamento distinto do devedor público11(3)a; limpar/trocar a escolha invalida a decisão corrente, sem apagar resultado histórico nem reutilizar campos ocultos como nova decisão.
Cálculo → gravação/GET pela API existente no QA55441 com rollback → recuperação → HTML/dossier demonstrados. Exemplo sintético: juros brutos60, imposto pago10 e fracção nacional8, com acordo qualificado → limite convencional0/crédito0/saldo708 MZN; sem reembolso, transporte ou consumo oficial e sem alterar L6. Relatório conserva instituição, financiamento e acordo. Prova `outputs/irpc-pt-mz-agreement-20261110/verification.json`; tipos/lint/Pint/build QA aprovados e provas anteriores reutilizadas.
Pacote `outputs/entrega-cdt-acordo-20261110.zip`, base exactab10b35f3, aplicação/reversão conferidas; entrega seguinte expressamente autorizada durante esta continuação, ainda sem instalação nesta entrada. Restantes beneficiários/regimes11(3)b, EP, relações especiais e demais CDT/benefícios/obrigações conservam correspondência própria; saldo L1–L7/restanteL8/L9–L16 preservado. Conferência visual continua pendente no mesmo registo; portal não integral.
## 10/11/2026 — CDT instalada; isenção governamental e precisão económica em QA
**Homologação: `60b52fc8c7446cbbee904a4411eca8ad284aa7ad`**, promovida às11:49:26UTC e conferida às11:50:11UTC (13:50 locais). Delta13hashes Git e revisão HTTPS iguais; portal/login/up/revision HTTP200, .env preservado, zero migrations/importações. Backup fresco/restauro190tabelas com contagens/checksums iguais e369ficheiros/configuração extraídos/conferidos; ac67f9c1 disponível para reversão. Recibos `outputs/cdt-pt-delivery-20261110/{backup,restore,files-restore,verification}.json`. Sessão SSH encerrada, credencial não guardada. Royalties/dividendos/juros ordinários PT–MZ deste recorte deixam de ser pendência de instalação; não reabrir provas aceites.
**Novos fechos dirigidos apenas em QA, ainda não instalados:**
- CDT11(3)a: devedor Estado português, subdivisão política/administrativa ou autarquia, expressamente qualificado e documentado; mantém residência, fonte, definição, período e restantes condições do recorte. Limite convencional0, imposto pago10 sem crédito elegível e saldo708 MZN no exemplo. Não gera reembolso/transporte nem altera L6. Beneficiários/acordos11(3)b e outras situações permanecem separados. Prova `outputs/irpc-pt-mz-government-20261110/verification.json`, com API/gravação/recuperação/HTML/dossier e fundamento do devedor no relatório.
- L6: diferença económica conserva fracções exactas, incluindo subcêntimos e n/d, pelo mecanismo racional existente. Remove a limitação técnica de exigir cêntimos inteiros neste mapa; não cria regra de arredondamento fiscal. Montante do tópico é reconciliado por igualdade racional, evitando tratar0,006 como0 ou aceitar truncamento. Exemplo de IRPC subjacente20,01: nominal12,006, efeito112,006, base4512,006, colecta1443,84192; reconferida L8, saldo723,83592. Repetição/substituição não acumula; valores já considerados/históricos preservados. Prova `outputs/irpc-economic-fraction-20261110/verification.json`, até API/recuperação/HTML/dossier/L8. Outros mapas e arredondamento de declarações oficiais conservam âmbito próprio.
Conjunto novo agrupável sobre60b52fc8; não integra a revisão instalada. Conferência visual permanece pendente no mesmo registo S08. Resíduos L1–L7, restante L8 e L9–L16 preservados; nenhum lote ou portal declarado integral. Próximos ramos: CDT11(3)b e restantes correspondências concretas, obrigações/benefícios e demais percursos conforme saldo existente.
Conjunto posterior preparado em `outputs/entrega-isencao-precisao-20261110.zip`, base exacta60b52fc8. Tipos/lint/Pint/build QA aprovados, aplicação/reversão conferidas em índices temporários. Não instalado; provas e pacotes anteriores preservados.
## 10/11/2026 — económico instalado; CDT Portugal avançada em QA
**Homologação: `ac67f9c13a6561b66c5da788a48849beac90a905`**, promovida pelo executor Git existente às11:32:14UTC e conferida às11:33:16UTC (13:33 locais). Revisão HTTPS e27hashes Git iguais; portal/login/up/revision HTTP200; .env preservado, zero migrations/importações. Backup/restauro190tabelas com contagens/checksums iguais e369ficheiros/configuração extraídos/conferidos. Release c359364d conservada. Recibos `outputs/economic-special-delivery-20261110/{backup,restore,files-restore,verification}.json`. Credencial não guardada; sessão SSH encerrada. Inclui quota económica, inclusão/reconciliação ordinária e especial L6, partilha64(3) e associações64(4). Entradas anteriores «apenas QA» são históricas nestes recortes; não reinstalar por consequência.
**Delta posterior, ainda QA:** correspondência CDT PT–MZ para royalties ordinárias não digitais, dividendos e juros ordinários de fonte portuguesa de residente convencional em Moçambique. País não activa a convenção. Exige beneficiário efectivo, definição/natureza, fonte/pagador, ausência de EP/instalação fixa ligada, período e suporte documentados; relações especiais verificadas nos juros/royalties, isenção governamental11(3) separada. Limite10% do bruto nos ramos suportados; dividendos usam alteração do Protocolo2009 art6, evitando taxa15% do texto1992. Não usa retenção L5 em sentido inverso nem tax sparing presumido. Mantém limite manual/históricos e parcela líquida nacional.
Prova sintética por ramo: bruto60 → limiteCDT6, imposto pago10/fracção nacional8 → crédito6 → saldo702 MZN. Cálculo/gravação/recuperação/HTML/dossier por API existente no QA55441; sem novo writer, schema, direito ou escrita activa. Provas `outputs/irpc-pt-mz-{credit,dividend,interest}-20261110/verification.json`. Fracções mantêm exactidão, sem arredondamento fiscal presumido. País/condição/período inválidos deixam apenas o caso dependente indeterminado. Este delta não integra ac67f9c1.
Restam CDT de outros Estados/naturezas, EP/instalação fixa, juros governamentais, excessos/relações especiais e casos fora do recorte, além dos demais resíduos L1–L7/L8/L9–L16. Conferência visual continua pendente no mesmo registo. Não declarar L8 ou o portal integralmente concluídos.
Conjunto CDT agrupado em `outputs/entrega-cdt-pt-20261110.zip`, base exacta ac67f9c1, aplicação/reversão conferidas; tipos/lint/Pint e build QA aprovados. Instalação deste delta ainda não executada.
## 10/11/2026 — crédito económico: partilha e associações em QA
Homologação mantém `c359364d`. Avanço posterior em QA, sem instalação: art64(3) distingue a parcela elegível do rendimento da partilha e o excesso com natureza de mais-valia; art64(4) distingue associação em participação e à quota, exigindo tributação efectiva documentada na participação. Reutiliza cálculo60%, prova do imposto subjacente, limites, prioridades, isolamento e writer privado existentes. Não calcula a mais-valia como crédito nem pressupõe inclusão L6 ou reconferência da colecta. Campos antigos e históricos são preservados; fontes incompletas ficam recuperáveis e indeterminadas.
Critério demonstrado: calcular → gravar/GET pela API existente no QA55441 com rollback → recuperar exactamente → HTML e relatório do dossier. Partilha sintética: valor atribuído200, entradas100 e rendimento documentado150 → elegível100/excesso50 → quota35,84/crédito12/saldo688 MZN. Associação documentada100/imposto20 → crédito12/saldo688 MZN, sem alterar L6. Provas `outputs/irpc-economic-liquidation-20261110/verification.json` e `outputs/irpc-economic-association-20261110/verification.json`. Não são actos fiscais nem consumo de saldos oficiais.
Conjunto de fontes compatíveis com quota e inclusão ordinária agrupado em `outputs/entrega-economica-especial-20261110.zip`, base c359364d, aplicação/reversão conferidas em índices temporários. Pacotes anteriores intactos. Inclusão/correcção especial em L6 também demonstrada em QA: reconcilia rendimento total da partilha (incluindo mais-valia) uma só vez, enquanto a quota usa apenas a componente elegível; associações reutilizam a qualificação própria. Prova outputs/irpc-economic-special-inclusion-20261110/verification.json: efeito162, base4562/colecta1459,84, quota35,84/saldo739,84 recuperados na API e relatórios. Casos transitórios, fracções de cêntimo, CDT, benefícios/obrigações e restantes resíduos L1–L7/L9–L16 continuam no saldo. Conferência visual pendente. Próximo trabalho funcional: correspondência CDT automática nos casos suportados pelas fontes existentes; eventual instalação deste conjunto é acção separada. Tipos/lint/Pint/build QA conferidos no conjunto final.
## 10/11/2026 — inclusão/correcção económica L6 em QA
Instalado: `c359364d`. Quota económica e inclusão/correcção L6 ordinária demonstradas em QA, sem instalação. Pacote anterior `outputs/entrega-economica-l6-l8-20261110.zip` preservado; novo delta posterior agrupado separadamente. Conferência visual pendente no registo existente.
Escolha explícita da origem L8, reconciliação de lucros/crédito já considerados, prévia da diferença e confirmação precedem o cálculo/gravação existentes. Repetição substitui a parcela; não acumula. Prova: +112 MZN leva lucro tributável a6812, base4512 e colecta1443,84. Montantes já incluídos100/12 dão efeito0; excesso150/20 corrige−58. Origem L8 anterior fica desactualizada; reconferida a nova colecta, quota35,84 e saldo723,84 mantêm-se na API/recuperação/HTML/dossier, com históricos preservados. Provas `outputs/irpc-economic-inclusion-20261110/verification.json` e `outputs/irpc-economic-quota-20261110/verification.json`. Tipos/lint/Pint/build QA aprovados.
Âmbito ordinário residente geral2026/MZN. Fracções de cêntimo exigem tratamento próprio, sem arredondamento presumido. Casos especiais, CDT e obrigações continuam no saldo. Resíduos L1–L7 e L9–L16 preservados; L6/L8 não integrais. Registos anteriores abaixo são históricos.
## Saldo operacional reconciliado — 10/11/2026, após `ff726e57`
**Estado mais recente:** conjunto Educação + fracção nacional ordinária instalado na homologação em `c359364de3f65599f2248540a5b040604e8490cc`, confirmado por HTTPS às 10:55:21 UTC (12:55 locais). Backup/restauro190tabelas e369ficheiros/configuração conferidos,14fontes do delta idênticas à revisão Git,zero migrations/importações,.env preservado e release ff726e57 mantida para reversão. Recibo `outputs/education-l8-delivery-20261110/verification.json`. As notas anteriores de conjunto «só QA» descrevem o estado anterior à instalação e não são nova pendência.
| Recorte | Estado actual |
| --- | --- |
| Educação — continuidade ao paginar histórico | Instalado c359364d; restantes jornadas/retoma durável e conferência visual conservadas. |
| L8 — fracção nacional ordinária do crédito internacional | Instalado c359364d; não reabrir cálculo/gravação/recuperação/relatórios aceites. |
| L8 — quota proporcional económica ordinária após acréscimo53 | Novo delta QA, ainda não instalado: cálculo, gravação/GET pela API existente, recuperação e relatórios demonstrados; tipos/lint aprovados. Inclusão/correcção automática do acréscimo na base não incluída. |
Novo residual avançado após a instalação: opção explícita de derivar a quota económica da colecta L6 × (lucros incluídos + crédito nominal já acrescido) / matéria colectável L6. CIRPC64(1–2)/53 e ordem já aceites,sem novo enquadramento jurídico genérico. Exige correspondência documentada à base corrente; origem alterada,acréscimo ausente ou componentes acima da base tornam o caso dependente indeterminado. Não reescreve L6,não acrescenta o crédito duas vezes,não altera o modo manual/históricos. Prova `outputs/irpc-economic-quota-20261110/verification.json`:quota256/5, saldo3244/5 no cenário sintético. L8 continua parcial,com inclusão/correcção económica,art64(3/4),CDT automática,obrigações e restantes casos ainda por implementar conforme saldo;L1–L7 eL9–L16 preservados.
### Actualização fiscal: recorte L8 demonstrado em QA; saldo L1–L16 preservado
**Pendência encerrada nesta passagem em QA:** derivação proporcional da fracção nacional do crédito internacional no caso L6 ordinário corrente, com parcela líquida já incluída e correspondida à matéria colectável. Percurso cálculo → gravação/recuperação pela API privada existente → recálculo → HTML/relatório do dossier demonstrado. Não é fecho integral de L8, determinação automática de CDT ou repartição de custos/perdas. Delta agrupável com a continuidade de Educação, ainda não instalado na homologação, que mantém `ff726e57`.
O resumo anterior centrado em L8 era incompleto. Conservam-se explicitamente os seguintes resíduos e lotes; os recibos posteriores identificados neste manual prevalecem sobre anotações antigas de ausência/instalação pendente. Nenhuma estimativa histórica ou contagem de testes representa conclusão do módulo.
| Lote fiscal | Saldo efectivo preservado |
| --- | --- |
| L1 — enquadramento IVA | Casos residuais de P058 e P001 fora dos tratamentos já entregues, incluindo o incremento P001 `d1551dab`/`63147312`. P084/P085 e restantes fechos operacionais aceites não são reabertos. |
| L2 — dedução/regularizações IVA | Art.18(6)/afectação mista, regularizações sucessivas/plurianuais, erros/incobráveis e variantes fora dos recortes entregues. |
| L3 — saldo/Modelo A | Sequência/reversões de deduções, apuramentos próprios de fornecimentos/margem/viagens/eventos e formalidades C/D/E não cobertas; entregas posteriores de restituição preservadas. |
| L4 — ISPC | Operações ocasionais, aquisições acima do limite e formulários/instruções/regulamentação de 2026 aplicáveis; apuramento habitual entregue preservado. |
| L5 — retenções IRPC | Casos documentais/AT e variantes de dispensa/CDT, juros/rectificações/guias não encerrados pelos recibos. Taxas gerais/especiais, câmbio e retenções sofridas já entregues conservados. |
| L6 — IRPC normal | Grupos de código e documentais discriminados no manual: perdas/deduções, períodos/moedas/representação e casos fora do recorte ordinário demonstrado. Colecta ordinária utilizável não é IRPC integral. |
| L7 — tratamentos específicos IRPC | Famílias/combinações/mistos/transporte e documentos por caso ainda abertos. Cumulações e ramos específicos entregues preservados. |
| L8 — liquidação IRPC | Derivação nacional ordinária encerrada em QA nesta passagem. Restam correspondência CDT automática, inclusão/correcção económica e quota automáticas, art.64(3/4)/benefícios, obrigações PPC/PEC, juros/guias, autónomos/períodos/moedas/M20M22 e casos transitórios/especiais. Transporte ordinário de créditos/PEC já entregue em `569a1cd7` não é tratado como ausente. |
| L9 — INSS | Percurso contributivo por trabalhador/vínculo/base/período e recuperação/declaração; referências de taxas e massa salarial não são fecho integral. |
| L10 — IRPS mensal | Tabela mensal do período, condições pessoais aplicáveis, remunerações/benefícios, retenção e líquido ligados a L9; preservar o caso anteriormente demonstrado, sem o extrapolar. |
| L11 — IRPS anual/outras categorias | Categorias, englobamento/tratamento próprio, deduções, retenções, saldo e formulários; não copiar regras IRPC. |
| L12 — Selo | Verbas por operação/contrato, incidência/base/taxa/isenção/obrigação com fonte e período; não aplicar exemplo genérico a todas as facturas. |
| L13 — direitos aduaneiros | Código pautal/origem/valor/despesas/câmbio/benefícios e percurso documental; não inferir código pela descrição nem benefício pela localização. |
| L14 — ICE | Produtos/operações, base/unidade e tributação específica/ad valorem/mista, período e ligações pertinentes; produção interna não depende de calcular direitos aduaneiros. |
| L15 — SISA | Delimitação legal/autárquica, transmissão onerosa/direitos, incidência/isenções/base/período e percurso; DUAT não é propriedade da terra. |
| L16 — IPRA/encargos imobiliários | Posse/uso/valor patrimonial, autarquia/exercício/isenções e separação de impostos e taxas administrativas; preço de compra não substitui valor patrimonial. |
Não foram identificados recibos de fecho integral de L9–L16 nesta reconciliação dirigida. Isso conserva o saldo; não apaga código, fontes ou casos parciais anteriores. Dúvidas/fontes em falta afectam apenas os casos dependentes. Detalhes e prova L8 na entrada actual do manual existente e em `outputs/irpc-national-fraction-20261110/recovered-reports.json`.
Esta é a actualização da visão e da matriz existentes, não um novo inventário. A matriz conserva 3.555 linhas sobrepostas de requisitos, cláusulas e campos; a contagem não representa funcionalidades independentes nem trabalho por implementar. Preservam-se todos os fechos e provas aceites. As secções datadas de 18/10 abaixo são históricas e não reabrem implementações posteriores.
Instalação de fontes até `ff726e57` concluída na homologação, incluindo S08, contador, aliases, contactos, pré-dossier, curadoria e sitemap. Prova: `outputs/reviewed-install-20261110/verification.json`, revisão HTTPS, 44 hashes do delta, configuração preservada, zero migrations/importações. Não reinstalar esse pacote. Código instalado não comprova por si só cada flag, ledger de instalação modular, canal externo ou percurso privado em uso real; as habilitações anteriores e as suas provas mantêm-se. Um módulo não é declarado desactivado apenas por falta de nova observação.
| Módulo / percurso utilizável a completar | Implementação que falta | Instalação e conferência | Dependência concreta / critério de conclusão |
| --- | --- | --- | --- |
| Descoberta, oportunidades e conteúdos públicos → interesse/dossier | Completar cláusulas e modalidades especializadas ainda abertas; não refazer cadastro, revisão, publicação, pesquisa ou curadoria entregues. | Fontes até ff726e57 instaladas; conferir apresentação/percursos ainda sem prova visual. | Dados efectivos de cada anúncio, divulgação e representação existentes. Fecho: localizar uma ficha, preparar/publicar pelo workflow aplicável e continuar no mesmo dossier sem perder contexto. |
| Territórios, mapa, âncoras/clusters → selecção contextual | Consumidores territoriais e correspondências ainda não cobertos; preservar mapa único, filtros e observações quantitativas entregues. | Não reabrir instalação dos deltas incluídos; conferir consumidores sem prova, sem repetir percursos aceites. | Geometrias, coordenadas, associações e fontes reais em falta por item. Fecho: pesquisar/seleccionar/guardar/recuperar a referência correcta, sem inventar equivalências ou pontos. |
| Balcão, percurso adaptativo e S08 → pedido/resultado | Restantes consumidores e parametrização por âmbito, obrigações/registo/licenciamento sustentados; núcleo de dossier, pedido, histórico e fecho pelo dono preservado. | S08 instalada. Conferência restante: #S08, estado, botão, gravação/recuperação, janela estreita. | Requisitos de cada serviço e actos externos próprios; bloquear só esse serviço/caso quando faltarem. Fecho: preparar/submeter/acompanhar/recuperar resultado no dossier certo. |
| CRM, responsáveis e tarefas → acompanhamento | Restantes formulários/consumidores e preparações não confirmadas; não recriar conversão, próxima acção, edição e preparação de tarefas já demonstradas. | Fontes instaladas; restantes variantes por perfil e uso visual a conferir. | Designações/representação do caso e integrações externas apenas onde usadas. Fecho: converter uma vez, atribuir, acompanhar e retomar sem duplicar tarefa/interacção. |
| Biblioteca, documentos, scanner e suporte → consulta privada | Cobertura do acervo e consumidores/variantes ainda não individualizados; preservar importação idempotente, originais, scanner, revogação e incidentes demonstrados. | Não reinstalar consulta pública/editor aceites; conferir anexos e consumidores pendentes. Operação de scanner/worker é distinta de código. | Bytes/fontes autorizados e disponibilidade efectiva dos serviços quando necessários. Fecho: importar/consultar/partilhar/revogar com original, autoria e histórico íntegros. |
| Simulador fiscal/contributivo → cálculo/rascunho/relatório | Resíduos L1–L7, restante L8 e L9–L16 conforme tabela acima. Fracção nacional ordinária agora demonstrada em QA; transporte/crédito económico já entregues preservados. Não limitar saldo fiscal a L8. | Fontes até ff726e57 instaladas; delta de fracção nacional ainda QA. Conferir apenas novos consumidores/deltas. | Diploma/período/limite/prova/CDT concretos faltantes bloqueiam só o cálculo dependente. Fecho por ramo: cenário calculado, guardado, recuperado e exportado com fonte/versão/valores coerentes. |
| Negócios, empresas e duplicados → ficha/publicação/comparação | Consolidação rastreável de entidades inequívocas, outros formatos e variantes contratuais ainda abertos. Pesquisa de semelhantes por raio/sector/classificação e consulta dos lotes já existem. | Fontes instaladas; visual/variantes ainda não observadas separados. | Pontos/fontes/classificações e prova do mandato por caso; não fundir apenas por nome. Fecho: localizar/importar/reconciliar, publicar e comparar sem perder originais/relações. |
| Fundos e alertas → referência/avaliação informativa/acompanhamento | Vínculos restantes da referência ao cadastro e canais/fontes externas; catálogo, editor, versões, critérios e gerador local já implementados. | Código instalado; confirmar operação do agendador/canal apenas no âmbito configurado e observação visual restante. | Fontes/prazos efectivos e credencial/destinatário do canal concreto. Fecho: consultar uma chamada, responder aos critérios, guardar e receber o aviso configurado sem decisão de crédito presumida. |
| Forma societária e preparação da empresa → modelo | Casos especiais; ingestão/versionamento/vínculo dos sete modelos exactos e derivadas; actividade/licenciamento integrado e percursos de assinatura/submissão/registo ainda não cobertos. | Comparador e preparação factual têm código instalado; não confundir ficha factual com contrato. Conferência visual pendente. | Exemplares exactos dos modelos e intervenientes externos de assinatura/registo. Fecho: comparar com fontes, preparar a forma escolhida e recuperar o modelo/derivada versionados; ausência de um modelo não bloqueia o comparador. |
| Capitalização e BVM → orientação/referência no dossier | Casos especiais e procedimentos CVM ainda não codificados; os guiões base de capitalização e cinco percursos BVM já existem. | Fontes instaladas; visual/login dos recortes pendentes. | Regulamento anual de montantes, preçário/encargos e condições da contraparte aplicáveis. Fecho: seleccionar percurso, perceber requisitos/fontes/indeterminações e recuperar a preparação no dossier. |
| Checklists e formulários → versão publicada/submissão | Outros consumidores e parametrização transversal de limites, formatos/mensagens e camadas documentais por fonte/âmbito. Editor, vigência, tipos, anexos e selector sem UUID manual já existem. | Fontes instaladas; habilitação nominal quando exigida pelo instalador existente, sem a presumir ausente; visual restante. | Texto/requisito/contexto publicado por administrador autorizado. Fecho: publicar v2, novas submissões usam v2 e respostas v1 continuam recuperáveis. |
| Educação → declaração/revisão/histórico | Retoma durável da preparação de revisão e jornadas externas de estudantes/formadoras/empregadores ainda abertas. **Avançado nesta passagem:** paginação do histórico preserva a revisão em preparação e o envio incerto, mantendo comparação/actor. | Delta novo local, ainda não instalado; quatro verificações novas, sem repetir provas anteriores. Conferência visual separada. | Não existe cadastro/documento aprovado dos vínculos externos, conforme decisão já registada; bloqueia só esses vínculos. Fecho interno: preparar/submeter, outro gestor rever e consultar histórico sem perder preparação ou duplicar decisão. |
| Bolsa/parcerias → candidatura/sala/acordo/marco | Variantes e consumidores não cobertos; preparação durável dos passos ainda só em memória. Editores, candidaturas, acordos, resumos e históricos entregues não são ausência funcional. | Fontes instaladas; conferência de partilha/revogação e restantes variantes por actor pendente. | Participantes/documentos autorizados no caso; não inferir autorização de oportunidade a partir da sala. Fecho: candidatura, colaboração, acordo/marco e revogação sem perda de original/histórico. |
| Perfis, gestão, indicadores e painéis → operação por papel | Métricas/fontes/denominadores e restantes filas por perfil; contextos de país em consumidores ainda abertos. Preservar perfis, revogação, suporte e editores entregues. | Código instalado; atribuições reais não são criadas por instalação; observação por papel separada. | Designações operacionais e fontes dos indicadores concretos. Fecho: acção útil permitida/negada pelo papel, evento contado com âmbito/período explícitos e distinção zero/sem dados. |
| Acesso offline, línguas e IA → consumidores previstos | Consulta offline de fichas já existe; edição/sincronização e idiomas adicionais não concluídos; IA não substitui os percursos determinísticos. | Código entregue preservado; abertura sem rede, apresentação e novos deltas por conferir. | Línguas/traduções aprovadas e fornecedor/credencial/âmbito de IA quando necessários; só bloqueiam esses casos. Fecho por consumidor, sem prometer cobertura geral offline ou inteligência não implementada. |
**Ordem de continuação preservada:** primeiro defeitos que impeçam usar/concluir percursos existentes (estabilização); depois simulador fiscal delimitado, Imobiliário, Bens de consumo/franquias, Estatística/painéis e IA, conforme ordem já registada. A correcção da continuidade do histórico de Educação pertence à estabilização de um percurso existente, não cria uma frente paralela. Próximo trabalho nessa ordem: continuar o saldo fiscal L8 executável com as fontes aceites; faltas de prova/CDT/regulamento bloqueiam apenas os casos afectados. A conferência visual S08 não suspende essa implementação.
O portal inteiro permanece em conclusão, sem excluir requisitos por inferência. A matriz conserva os critérios originais e as provas por ID; este quadro organiza o trabalho por percursos, sem promover módulos agregados a concluídos por código instalado ou por um teste parcial.
<!-- saldo-atual-20261018 -->
## Saldo atual por requisito em 18 de outubro de 2026
+2 -1
View File
@@ -1,6 +1,7 @@
/* Presentation choice for Tempo's 21/10/2026 request: one shared navigation. */
.tempo-header {
position: relative;
position: sticky;
top: 0;
z-index: 40;
background: #090909;
color: #fff;
@@ -0,0 +1,49 @@
import type { ReactNode } from 'react';
type Props = {
badge: string;
title: string;
description: string;
children: ReactNode;
footer: ReactNode;
};
export default function AuthTempoCard({
badge,
title,
description,
children,
footer,
}: Props) {
return (
<div className="w-full overflow-hidden rounded-3xl bg-slate-50 shadow-2xl ring-1 ring-black/5">
<div className="p-5 sm:p-8 lg:p-10">
<img
src="/images/logo/logo-vermelho.png"
alt="Tempo"
width="1787"
height="564"
className="mx-auto mb-4 h-14 w-auto"
/>
<div className="mx-auto mb-8 max-w-2xl text-center">
<span className="mb-3 inline-flex rounded-full bg-red-100 px-3 py-1 text-xs font-bold tracking-wide text-[#b51219] uppercase">
{badge}
</span>
<h2 className="text-2xl font-bold text-slate-950 sm:text-3xl">
{title}
</h2>
<p className="mt-3 text-sm leading-6 text-slate-600">
{description}
</p>
</div>
{children}
</div>
<div className="border-t border-slate-200 bg-white p-5 text-center text-sm text-slate-600">
{footer}
</div>
</div>
);
}
@@ -18,7 +18,11 @@ export default function CatalogueInvestmentMap({
territoryId?: string;
mapKind?: InvestmentMapKind;
}) {
const { context, error } = useTerritoryGeometry(territoryId);
const { context, error } = useTerritoryGeometry(
territoryId,
undefined,
true,
);
return (
<InteractiveInvestmentMap
points={points}
@@ -54,14 +58,15 @@ export default function CatalogueInvestmentMap({
Sem limite administrativo acessível para o
território seleccionado.
{context.areas.length > 0
? ' Os limites de níveis superiores são apenas contexto e não representam a selecção.'
? ' Os limites disponíveis são contexto administrativo e não substituem o limite da selecção.'
: ''}
</p>
)}
{context?.truncated && (
<p role="alert">
Cobertura administrativa incompleta: são
apresentados até 100 limites.
consultadas até 100 geometrias e 500
subdivisões.
</p>
)}
</>
+41 -1
View File
@@ -16,6 +16,46 @@ export default function EducationReview(props: EducationReviewProps) {
aria-label="Revisão interna da declaração"
>
{edit.notice && <p role="status">{edit.notice}</p>}
{edit.preparationAvailable && (
<section
className="space-y-2 rounded border p-3"
aria-label="Preparação privada da revisão"
>
<p>
Guarde a preparação para retomar noutra entrada. É
privada desta conta e declaração; guardar não decide nem
aprova a declaração.
</p>
{edit.savedPreparation?.version ? (
<p>
Preparação guardada: versão{' '}
{edit.savedPreparation.version}.
</p>
) : (
<p>Ainda sem preparação privada guardada.</p>
)}
<button
type="button"
className={button}
disabled={edit.persisting}
onClick={() => void edit.persistPreparation()}
>
Guardar preparação da revisão
</button>
<button
type="button"
className={button}
disabled={edit.persisting || form.processing}
onClick={() => void edit.restorePreparation()}
>
Recuperar preparação guardada
</button>
{edit.persisting && (
<p role="status">A conferir a preparação privada…</p>
)}
</section>
)}
{(edit.stale || edit.conflict) && (
<section
className="space-y-3 rounded border p-4"
@@ -155,7 +195,7 @@ export default function EducationReview(props: EducationReviewProps) {
</h2>
{edit.uncertain && (
<p>
A recuperação repete a decisão enviada:{' '}
A recuperação mantém a mesma intenção de revisão:{' '}
{states[edit.sent?.status ?? ''] ?? 'Por confirmar'}
. Fundamento: {edit.sent?.review_reason}. As edições
posteriores ficam conservadas.
@@ -0,0 +1,157 @@
import { card, input } from '@/pages/investment/shared';
export const financialCategories: Record<string, string> = {
banking: 'Banca',
credit: 'Crédito e microfinanças',
investment: 'Gestão de investimento',
pensions: 'Fundos de pensões',
insurance: 'Seguros e resseguros',
intermediation: 'Mediação de seguros',
exchange: 'Câmbio',
payments: 'Pagamentos e serviços interbancários',
supervision: 'Supervisão',
other: 'Outras instituições',
};
export const financialTypes: Record<string, string> = {
commercial_bank: 'Banco comercial',
investment_bank: 'Banco de investimento',
bank: 'Banco (tipo não especificado)',
microbank: 'Microbanco',
cooperative: 'Cooperativa de crédito',
savings: 'Organização de poupança e empréstimo',
investment_manager: 'Gestora de fundos de investimento',
investment_company: 'Sociedade de investimento',
pension_manager: 'Gestora de fundos de pensões',
insurer: 'Seguradora',
reinsurer: 'Resseguradora',
microinsurer: 'Micro-seguradora',
broker: 'Corretora de seguros',
agent: 'Agente de seguros',
reinsurance_broker: 'Corretor de resseguro',
exchange_house: 'Casa de câmbio',
electronic_money: 'Instituição de moeda electrónica',
interbank: 'Serviços interbancários',
supervisor: 'Supervisor',
other: 'Outro tipo documentado',
};
export type Classification = {
institution_categories?: string[];
institution_types?: string[];
institution_services?: string[];
institution_status?: string;
successor_key?: string | null;
register_reference?: string | null;
source_document_name?: string | null;
source_document_sha256?: string | null;
aliases?: string[];
source_rows?: { id: string; section: string; text: string }[];
};
export const evidenceLabels: Record<string, string> = {
reported: 'Reportado no documento',
to_confirm: 'A confirmar',
historical: 'Referência histórica',
conflict: 'Informação divergente/identidade por confirmar',
unlocated: 'Localização por confirmar',
};
export function ClassificationSummary({ value }: { value: Classification }) {
return (
<section className={card}>
<h2>Categoria e tipo da instituição</h2>
<p>
Categorias:{' '}
{(value.institution_categories ?? [])
.map((k) => financialCategories[k] ?? k)
.join(' · ') || 'Por classificar'}
</p>
<p>
Tipos:{' '}
{(value.institution_types ?? [])
.map((k) => financialTypes[k] ?? k)
.join(' · ') || 'Por classificar'}
</p>
{['merged','historical','unknown'].includes(value.institution_status??'') && <p>{value.institution_status==='merged'?'Instituição integrada noutra entidade ou com actividade encerrada.':value.institution_status==='historical'?'Informação histórica.':'Situação actual por confirmar junto da instituição.'}</p>}
</section>
);
}
export function ClassificationFields({
value,
change,
needs,
}: {
value: Classification;
change: (patch: Partial<Classification>) => void;
needs: Record<string, string>;
}) {
const multiple = (
key:
| 'institution_categories'
| 'institution_types'
| 'institution_services',
label: string,
options: Record<string, string>,
) => (
<label className="my-3 block">
{label}
<select
multiple
className={input}
value={value[key] ?? []}
onChange={(e) =>
change({
[key]: Array.from(
e.target.selectedOptions,
(o) => o.value,
),
})
}
>
{Object.entries(options).map(([id, title]) => (
<option key={id} value={id}>
{title}
</option>
))}
</select>
</label>
);
return (
<details className={card}>
<summary>Categoria, tipo e serviço — Alterar</summary>
{multiple(
'institution_categories',
'Categorias documentadas',
financialCategories,
)}
{multiple(
'institution_types',
'Tipos documentados',
financialTypes,
)}
{multiple(
'institution_services',
'Serviços identificados (não é oferta garantida)',
needs,
)}
<label>
Situação
<select
className={input}
value={value.institution_status ?? 'unknown'}
onChange={(e) =>
change({ institution_status: e.target.value })
}
>
<option value="unknown">Por confirmar</option>
<option value="reported">Reportada no documento</option>
<option value="historical">Histórica</option>
<option value="merged">
Fundida/extinta segundo a fonte
</option>
</select>
</label>
<p>
As linhas originais e as classificações anteriores conservam-se
nas versões. Não complete lacunas por inferência.
</p>
</details>
);
}
@@ -127,8 +127,8 @@ export default function FundingCriteriaAssessment({
)}
{criteria.length === 0 && (
<p>
Esta ficha não contém critérios suficientes para conferir a
correspondência.
Consulte a entidade para conhecer as condições de acesso
aplicáveis ao seu investimento.
</p>
)}
{awaitingType > 0 && (
@@ -0,0 +1,243 @@
import { Link, router } from '@inertiajs/react';
import { useRef, useState } from 'react';
import { profileJson, ProfileValidationError } from '@/lib/profile-json';
import { createOperationKey } from '@/lib/operation-key';
import { card, input, button } from '@/pages/investment/shared';
import type { Programme } from '@/pages/funding/shared';
export type FundingDossier = {
id: string;
version: number;
opportunity: string | null;
closed: boolean;
href: string;
};
export function FundingDossierChoice({
record,
dossier,
selection,
needs,
destinations,
}: {
record: Programme;
dossier: FundingDossier | null;
selection: Record<string, string>;
needs: Record<string, string>;
destinations: { id: string; opportunity: string | null; title: string }[];
}) {
const [need, setNeed] = useState(selection.need ?? '');
const [product, setProduct] = useState('');
const [location, setLocation] = useState('');
const [consent, setConsent] = useState(false);
const [busy, setBusy] = useState(false);
const [error, setError] = useState('');
const [result, setResult] = useState<FundingDossier | null>(null);
const pending = useRef<Record<string, unknown> | null>(null);
const running = useRef(false);
async function save() {
if (!dossier || running.current) return;
if (!pending.current)
pending.current = {
context: dossier.id,
opportunity: dossier.opportunity,
version: dossier.version,
version_id: record.version_id,
product: product || null,
location_id: location || null,
location_mode: selection.location_mode ?? 'coverage',
need,
territory: selection.territory,
consent,
operation_key: createOperationKey(),
};
running.current = true;
setBusy(true);
setError('');
try {
setResult(
(await profileJson(
`/fundos/${record.id}/dossier`,
pending.current,
'POST',
)) as FundingDossier,
);
pending.current = null;
} catch (e) {
setError(
e instanceof Error
? e.message
: 'Resultado por confirmar. Repita a mesma intenção.',
);
if (
e instanceof ProfileValidationError &&
e.status !== null &&
e.status >= 400 &&
e.status < 500 &&
e.status !== 408
)
pending.current = null;
} finally {
running.current = false;
setBusy(false);
}
}
return (
<section className={card}>
<h2>Continuar a preparação no meu dossier</h2>
<p>
Conserva a referência, versão, produto, local e necessidade. Não
envia candidatura ou dados à instituição.
</p>
{!dossier ? (
<label>
Escolher dossier e oportunidade existentes
<select
className={input}
defaultValue=""
onChange={(e) => {
const chosen = destinations[Number(e.target.value)];
if (chosen)
router.get(`/fundos/${record.id}`, {
...selection,
context: chosen.id,
opportunity: chosen.opportunity ?? '',
});
}}
>
<option value="">Seleccionar</option>
{destinations.map((d, i) => (
<option key={`${d.id}:${d.opportunity}`} value={i}>
{d.title}
</option>
))}
</select>
</label>
) : result ? (
<Link className={button} href={result.href}>
Escolha guardada — continuar no mesmo dossier
</Link>
) : (
<>
<fieldset
disabled={
busy || Boolean(pending.current) || dossier.closed
}
>
<label>
Necessidade
<select
className={input}
value={need}
onChange={(e) => setNeed(e.target.value)}
>
<option value="">Seleccionar</option>
{Object.entries(needs).map(([id, label]) => (
<option key={id} value={id}>
{label}
</option>
))}
</select>
</label>
{record.payload.kind === 'institution' &&
(record.payload.products ?? []).length > 0 && (
<label>
Programa/produto
<select
className={input}
value={product}
onChange={(e) =>
setProduct(e.target.value)
}
>
<option value="">
Seleccionar e conferir
cobertura/condições
</option>
{record.payload.products?.map((p) => (
<option key={p.code} value={p.code}>
{p.title} ·{' '}
{p.availability === 'unknown'
? 'Disponibilidade por confirmar'
: p.availability ===
'closed'
? 'Encerrado'
: 'Oferta identificada'}
</option>
))}
</select>
</label>
)}
{record.payload.kind === 'institution' && (
<label>
Sede/balcão a conservar na preparação
<select
className={input}
value={location}
onChange={(e) =>
setLocation(e.target.value)
}
>
<option value="">
Sem local específico seleccionado / por
confirmar
</option>
{(record.payload.locations ?? [])
.filter((l) => l.id)
.map((l) => (
<option key={l.id} value={l.id}>
{l.label} ·{' '}
{l.location_text ||
l.territory_path
?.map((n) => n.name)
.join(' / ') ||
'Local por confirmar'}{' '}
·{' '}
{l.evidence_status ===
'to_confirm'
? 'A confirmar'
: ''}
</option>
))}
</select>
</label>
)}
<p>
Local seleccionado:{' '}
{selection.territory
? selection.territory_label ||
'Conservado da pesquisa; consulte a cobertura do produto.'
: 'Local de interesse por identificar; a referência fica guardada com essa lacuna explícita.'}
</p>
<label>
<input
type="checkbox"
checked={consent}
onChange={(e) => setConsent(e.target.checked)}
/>
Conservar esta referência privada no dossier;
financiamento não garantido.
</label>
</fieldset>
<button
type="button"
className={button}
disabled={
busy ||
dossier.closed ||
(!pending.current && (!consent || !need))
}
onClick={() => void save()}
>
{pending.current
? 'Repetir a mesma intenção'
: 'Guardar e continuar no dossier'}
</button>
{dossier.closed && (
<p>Dossier encerrado; consulta preservada.</p>
)}
</>
)}
{error && <p role="alert">{error}</p>}
</section>
);
}
@@ -0,0 +1,716 @@
import {
ClassificationFields,
evidenceLabels,
type Classification,
} from '@/components/funding-classification';
import {
fundingPublicPresentation,
publicInstitutionContacts,
publicInstitutionLocations,
} from '@/lib/funding-public-presentation';
import {
financialCategories,
financialTypes,
} from '@/components/funding-classification';
import { card, input, button } from '@/pages/investment/shared';
export type FundingSource = {
source_url: string | null;
source_document_name?: string | null;
source_document_sha256?: string | null;
evidence_status?: string | null;
origin?: string;
source_refs?: string[];
source_reference: string;
checked_on: string;
};
export type FundingContact = FundingSource & { kind: string; value: string };
export type FundingLocation = FundingSource & {
id?: string;
location_text?: string;
hours?: string;
role: string;
label: string;
address: string;
territory_id: string;
territory_path?: { name: string }[];
};
export type FundingProduct = FundingSource & {
code: string;
title: string;
instrument: string;
needs: string[];
coverage: string;
coverage_text: string;
territory_ids: string[];
territory_paths?: { name: string }[][];
conditions: string;
amounts: string;
availability: string;
contact_url: string;
};
export type InstitutionDetails = Classification & {
institution_key: string;
institution_nature: string;
contacts: FundingContact[];
locations: FundingLocation[];
products: FundingProduct[];
};
export const emptyInstitution: InstitutionDetails = {
institution_key: '',
institution_nature: 'unknown',
contacts: [],
locations: [],
products: [],
};
const source: FundingSource = {
source_url: '',
source_reference: '',
checked_on: '',
};
function Text({
label,
value,
change,
type = 'text',
}: {
label: string;
value: string;
change: (v: string) => void;
type?: string;
}) {
return (
<label className="my-2 block">
{label}
<input
className={input}
type={type}
value={value ?? ''}
maxLength={type === 'text' ? 1500 : undefined}
onChange={(e) => change(e.target.value)}
/>
</label>
);
}
function Sources({
value,
change,
}: {
value: FundingSource;
change: (v: Partial<FundingSource>) => void;
}) {
return (
<details>
<summary>Fonte e data deste elemento — Alterar</summary>
<Text
label="Endereço da fonte"
type="url"
value={value.source_url ?? ''}
change={(source_url) => change({ source_url })}
/>
{!value.source_url && (
<>
<Text
label="Documento de origem"
value={value.source_document_name ?? ''}
change={(source_document_name) =>
change({ source_document_name })
}
/>
<Text
label="SHA-256 do original"
value={value.source_document_sha256 ?? ''}
change={(source_document_sha256) =>
change({ source_document_sha256 })
}
/>
</>
)}
{value.source_document_sha256 && (
<label>
Situação desta informação
<select
className={input}
value={value.evidence_status ?? 'to_confirm'}
onChange={(e) =>
change({ evidence_status: e.target.value })
}
>
{Object.entries(evidenceLabels).map(([key, label]) => (
<option key={key} value={key}>
{label}
</option>
))}
</select>
</label>
)}
<Text
label="Referência/edição"
value={value.source_reference}
change={(source_reference) => change({ source_reference })}
/>
<Text
label="Data de consulta"
type="date"
value={value.checked_on}
change={(checked_on) => change({ checked_on })}
/>
</details>
);
}
export function InstitutionFields({
value,
change,
territories,
natures,
needs,
}: {
value: InstitutionDetails;
change: (patch: Partial<InstitutionDetails>) => void;
territories: { id: string | number; name?: string }[];
natures: Record<string, string>;
needs: Record<string, string>;
}) {
const update = <K extends 'contacts' | 'locations' | 'products'>(
key: K,
index: number,
patch: Partial<InstitutionDetails[K][number]>,
) =>
change({
[key]: value[key].map((row, i) =>
i === index ? { ...row, ...patch } : row,
),
});
return (
<section className={card}>
<h2>Instituição, presença física e produtos</h2>
<p>
A sede ou balcão não determina a cobertura do financiamento. Não
preencha condições desconhecidas por inferência.
</p>
<ClassificationFields value={value} change={change} needs={needs} />
<Text
label="Identidade estável da instituição (referência do cadastro/fonte)"
value={value.institution_key}
change={(institution_key) => change({ institution_key })}
/>
<label>
Natureza
<select
className={input}
value={value.institution_nature}
onChange={(e) =>
change({ institution_nature: e.target.value })
}
>
{Object.entries(natures).map(([id, label]) => (
<option key={id} value={id}>
{label}
</option>
))}
</select>
</label>
<details>
<summary>Contactos ({value.contacts.length}) — Alterar</summary>
{value.contacts.map((row, i) => (
<fieldset className={card} key={i}>
<legend>Contacto {i + 1}</legend>
<select
className={input}
value={row.kind}
onChange={(e) =>
update('contacts', i, { kind: e.target.value })
}
>
{Object.entries({
website: 'Página institucional',
email: 'Email',
phone: 'Telefone',
address: 'Endereço postal',
}).map(([id, label]) => (
<option key={id} value={id}>
{label}
</option>
))}
</select>
<Text
label="Contacto documentado"
value={row.value}
change={(v) => update('contacts', i, { value: v })}
/>
<Sources
value={row}
change={(v) => update('contacts', i, v)}
/>
<button
type="button"
onClick={() =>
change({
contacts: value.contacts.filter(
(_, n) => n !== i,
),
})
}
>
Remover contacto
</button>
</fieldset>
))}
<button
type="button"
className={button}
disabled={value.contacts.length >= 20}
onClick={() =>
change({
contacts: [
...value.contacts,
{ ...source, kind: 'website', value: '' },
],
})
}
>
Acrescentar contacto
</button>
</details>
<details>
<summary>
Sede e balcões ({value.locations.length}) — Alterar
</summary>
{value.locations.map((row, i) => (
<fieldset className={card} key={i}>
<legend>Local físico {i + 1}</legend>
<select
className={input}
value={row.role}
onChange={(e) =>
update('locations', i, { role: e.target.value })
}
>
<option value="headquarters">Sede</option>
<option value="branch">Balcão</option>
<option value="contact_point">
Contacto institucional — tipo por confirmar
</option>
</select>
<Text
label="Designação"
value={row.label}
change={(label) =>
update('locations', i, { label })
}
/>
<Text
label="Morada documentada"
value={row.address}
change={(address) =>
update('locations', i, { address })
}
/>
<label>
Local do cadastro
<select
className={input}
value={row.territory_id ?? ''}
onChange={(e) =>
update('locations', i, {
territory_id: e.target.value,
})
}
>
<option value="">
Correspondência por confirmar
</option>
{territories.map((t) => (
<option key={t.id} value={t.id}>
{t.name}
</option>
))}
</select>
</label>
<Sources
value={row}
change={(v) => update('locations', i, v)}
/>
<button
type="button"
onClick={() =>
change({
locations: value.locations.filter(
(_, n) => n !== i,
),
})
}
>
Remover local
</button>
</fieldset>
))}
<button
type="button"
className={button}
disabled={value.locations.length >= 50}
onClick={() =>
change({
locations: [
...value.locations,
{
...source,
role: 'headquarters',
label: '',
address: '',
territory_id: '',
},
],
})
}
>
Acrescentar sede/balcão
</button>
</details>
<details>
<summary>
Programas/produtos ({value.products.length}) — Alterar
</summary>
{value.products.map((row, i) => (
<fieldset className={card} key={row.code}>
<legend>Produto {i + 1}</legend>
<Text
label="Nome documentado"
value={row.title}
change={(title) => update('products', i, { title })}
/>
<Text
label="Instrumento"
value={row.instrument}
change={(instrument) =>
update('products', i, { instrument })
}
/>
<label>
Necessidades documentadas
<select
multiple
className={input}
value={row.needs}
onChange={(e) =>
update('products', i, {
needs: Array.from(
e.target.selectedOptions,
(o) => o.value,
),
})
}
>
{Object.entries(needs).map(([id, label]) => (
<option key={id} value={id}>
{label}
</option>
))}
</select>
</label>
<label>
Cobertura deste produto
<select
className={input}
value={row.coverage}
onChange={(e) =>
update('products', i, {
coverage: e.target.value,
territory_ids: [],
})
}
>
<option value="unknown">Por confirmar</option>
<option value="national">
Nacional, segundo a fonte
</option>
<option value="territorial">
Locais documentados
</option>
</select>
</label>
{row.coverage === 'territorial' && (
<label>
Locais abrangidos
<select
multiple
className={input}
value={row.territory_ids}
onChange={(e) =>
update('products', i, {
territory_ids: Array.from(
e.target.selectedOptions,
(o) => o.value,
),
})
}
>
{territories.map((t) => (
<option key={t.id} value={t.id}>
{t.name}
</option>
))}
</select>
</label>
)}
<Text
label="Explicação da cobertura"
value={row.coverage_text}
change={(coverage_text) =>
update('products', i, { coverage_text })
}
/>
<label>
Condições — vazio significa por confirmar
<textarea
className={input}
value={row.conditions}
maxLength={4000}
onChange={(e) =>
update('products', i, {
conditions: e.target.value,
})
}
/>
</label>
<Text
label="Montantes/moeda/prazos documentados — se conhecidos"
value={row.amounts}
change={(amounts) =>
update('products', i, { amounts })
}
/>
<label>
Disponibilidade
<select
className={input}
value={row.availability}
onChange={(e) =>
update('products', i, {
availability: e.target.value,
})
}
>
<option value="unknown">Por confirmar</option>
<option value="documented">
Oferta identificada na fonte (não é
concessão)
</option>
<option value="closed">
Encerrada segundo a fonte
</option>
</select>
</label>
<Text
label="Canal de contacto do produto"
type="url"
value={row.contact_url}
change={(contact_url) =>
update('products', i, { contact_url })
}
/>
<Sources
value={row}
change={(v) => update('products', i, v)}
/>
<button
type="button"
onClick={() =>
change({
products: value.products.filter(
(_, n) => n !== i,
),
})
}
>
Remover produto
</button>
</fieldset>
))}
<button
type="button"
className={button}
disabled={value.products.length >= 40}
onClick={() =>
change({
products: [
...value.products,
{
...source,
code: crypto.randomUUID(),
title: '',
instrument: '',
needs: [],
coverage: 'unknown',
coverage_text: '',
territory_ids: [],
conditions: '',
amounts: '',
availability: 'unknown',
contact_url: '',
},
],
})
}
>
Acrescentar programa/produto
</button>
</details>
</section>
);
}
export function InstitutionView({
value,
compact = false,
}: {
value: InstitutionDetails & { title?: string; summary?: string };
compact?: boolean;
}) {
const presentation = fundingPublicPresentation({
...value,
title: value.title ?? '',
summary: value.summary ?? '',
kind: 'institution',
}),
contacts = publicInstitutionContacts(value.contacts),
locations = publicInstitutionLocations(value.locations);
const category = (value.institution_categories ?? [])
.map((k) => financialCategories[k])
.filter(Boolean)
.join(' · '),
types = (value.institution_types ?? [])
.map((k) => financialTypes[k])
.filter(Boolean)
.join(' · ');
const urls = new Set<string>(
compact
? []
: presentation.products
.map((p) => p.url)
.filter((url): url is string => !!url),
);
const sites = contacts
.filter((c) => c.kind === 'website')
.filter((c) => {
if (urls.has(c.value)) return false;
urls.add(c.value);
return true;
});
return (
<section className="space-y-4" aria-label="Informação da instituição">
{!compact && (
<h2 className="text-lg font-semibold">
Instituição financeira
</h2>
)}
<div>
<h3 className="font-semibold">Descrição</h3>
<p>{presentation.description}</p>
</div>
{(category || types) && (
<p>
{category}
{category && types ? ': ' : ''}
{types}
</p>
)}
{['historical', 'merged'].includes(
value.institution_status ?? '',
) && <p>Instituição histórica ou integrada noutra entidade.</p>}
<div>
<h3 className="font-semibold">Produtos e serviços</h3>
{presentation.products.length ? (
<ul className="list-disc space-y-2 pl-5">
{presentation.products.map((p) => (
<li key={p.title}>
{!compact && p.url ? (
<a
className="font-semibold underline"
href={p.url}
target="_blank"
rel="noopener noreferrer"
>
{p.title}
</a>
) : (
<strong>{p.title}</strong>
)}
{p.detail ? ': ' + p.detail : ''}
</li>
))}
</ul>
) : presentation.services.length ? (
<p>{presentation.services.join(' · ')}</p>
) : null}
</div>
{!compact &&
(sites.length > 0 ||
contacts.some((c) => c.kind !== 'website')) && (
<div>
<h3 className="font-semibold">Contactos</h3>
<ul className="space-y-1">
{sites.map((c) => (
<li key={c.value}>
<a
className="underline"
href={c.value}
target="_blank"
rel="noopener noreferrer"
>
Website
</a>
</li>
))}
{contacts
.filter((c) => c.kind !== 'website')
.map((c) => (
<li key={c.kind + c.value}>
{c.kind === 'email' ? (
<a
className="underline"
href={'mailto:' + c.value}
>
{c.value}
</a>
) : c.kind === 'phone' ? (
<a
className="underline"
href={
'tel:' +
c.value.replace(
/[^+0-9]/g,
'',
)
}
>
{c.value}
</a>
) : (
c.value
)}
</li>
))}
</ul>
</div>
)}
{!compact && locations.length > 0 && (
<div>
<h3 className="font-semibold">Localização</h3>
<ul className="space-y-1">
{locations.map((l, i) => (
<li key={i}>
{l.role === 'headquarters'
? 'Sede: '
: l.role === 'branch'
? 'Balcão: '
: ''}
{l.address}
{l.hours ? ' · ' + l.hours : ''}
</li>
))}
</ul>
</div>
)}
</section>
);
}
+3 -1
View File
@@ -1,17 +1,19 @@
import type { HTMLAttributes } from 'react';
import { cn } from '@/lib/utils';
import { useTempoUi } from '@/components/tempo-brand';
export default function InputError({
message,
className = '',
...props
}: HTMLAttributes<HTMLParagraphElement> & { message?: string }) {
const { t } = useTempoUi();
return message ? (
<p
{...props}
className={cn('text-sm text-red-600 dark:text-red-400', className)}
>
{message}
{t(message)}
</p>
) : null;
}
@@ -1,7 +1,54 @@
import { useEffect, useState } from 'react';
import { useTempoUi } from '@/components/tempo-brand';
import { translateText } from '@/translation/service';
import { protectTranslationTokens, restoreTranslationTokens } from '@/translation/utils';
// Bridge the original Trans calls to this platform's existing language context.
export function Trans({ children }: { children: string }) {
const { t } = useTempoUi();
return <span>{t(children)}</span>;
// Only the fixed public institutional texts using this component are sent to Tempo.
const cache = new Map<string, string>();
const pending = new Map<string, Promise<string>>();
function translateInstitutionalText(text: string, language: string): Promise<string> {
const key = JSON.stringify([language, text]);
const cached = cache.get(key);
if (cached) return Promise.resolve(cached);
const existing = pending.get(key);
if (existing) return existing;
const protectedText = protectTranslationTokens(text);
const request = translateText({
text: protectedText.text,
sourceLanguage: 'pt',
targetLanguage: language,
}).then(({ translatedText }) => {
// Do not accept a response that lost an identifier, URL or protected term.
for (const token of protectedText.replacements.keys()) {
if (!translatedText.includes(token)) throw new Error('Translation lost a protected token');
}
const result = restoreTranslationTokens(translatedText, protectedText.replacements);
// Tempo's API returns the input on upstream failure: do not cache it as success.
if (result !== text) cache.set(key, result);
return result;
}).finally(() => pending.delete(key));
pending.set(key, request);
return request;
}
export function Trans({ children }: { children: string }) {
const { language, t } = useTempoUi();
const localText = t(children);
const [result, setResult] = useState<{ key: string; text: string } | null>(null);
const key = JSON.stringify([language, children]);
useEffect(() => {
if (language === 'pt' || localText !== children || !children.trim()) return;
let active = true;
translateInstitutionalText(children, language).then((text) => {
if (active) setResult({ key, text });
}).catch(() => {
if (active) setResult(null);
});
return () => { active = false; };
}, [children, key, language, localText]);
return <span data-no-translate>{result?.key === key ? result.text : localText}</span>;
}
@@ -24,7 +24,9 @@ export type InvestmentMapFeature = {
};
};
export type InvestmentMapArea = {
opportunity_id?: number;
selected_territory?: boolean;
child_territory?: boolean;
location_type?: string;
purpose?: string;
geometry: GeoJsonObject;
@@ -67,7 +69,7 @@ export default function InteractiveInvestmentMap({
}[mapKind];
const areaDescription = (area: InvestmentMapArea) =>
area.purpose === 'administrative_boundary'
? 'Limite administrativo da edição indicada; não representa um local de investimento'
? `${area.selected_territory ? 'Território seleccionado' : area.child_territory ? 'Subdivisão do território seleccionado' : 'Contexto de nível superior'} · Limite administrativo da edição indicada; não representa um local de investimento`
: area.purpose === 'declared_parcel'
? 'Perímetro declarado pelo Promotor; não é limite administrativo nem comprova disponibilidade legal'
: mapKind === 'documented_locations'
@@ -0,0 +1,378 @@
import { useId, useState } from 'react';
export type QuantitativeMapContext = {
state: 'available' | 'unavailable' | 'not_included' | 'error';
records: {
id: string;
zone_id: string;
zone_name: string;
zone_version: number;
url: string;
theme: 'workforce' | 'economic_potential';
indicator: string;
label?: string | null;
description?: string | null;
value: string | null;
unit?: string | null;
period_start?: string | null;
period_end?: string | null;
scope?: string | null;
population?: string | null;
methodology?: string | null;
source_reference?: string | null;
source_date?: string | null;
knowledge: string;
missing_metadata: string[];
}[];
zones_consulted?: number;
zone_limit?: number;
record_limit?: number;
truncated: boolean;
};
const labels: Record<string, string> = {
employment: 'Emprego',
investment: 'Investimento',
production: 'Produção',
exports: 'Exportações',
local_procurement: 'Compras locais',
};
const metadataLabels: Record<string, string> = {
unit: 'unidade',
period_start: 'início do período',
period_end: 'fim do período',
scope: 'âmbito',
population: 'população/base',
methodology: 'metodologia',
source_reference: 'fonte',
source_date: 'data da fonte',
};
const text = (value?: string | null) => value?.trim() || 'Não indicado';
const completeDate = (value?: string | null) =>
Boolean(
value &&
/^\d{4}-\d{2}-\d{2}$/.test(value) &&
!Number.isNaN(Date.parse(value)) &&
new Date(value).toISOString().slice(0, 10) === value,
);
export function filterMapIndicators(
records: QuantitativeMapContext['records'],
indicator: string,
start: string,
end: string,
) {
if (start && end && start > end) return [];
return records.filter((row) => {
if (indicator !== 'all' && row.indicator !== indicator) return false;
if (!start && !end) return true;
// Incomplete or inverted source periods cannot establish overlap.
if (
!completeDate(row.period_start) ||
!completeDate(row.period_end) ||
!row.period_start ||
!row.period_end ||
row.period_start > row.period_end
)
return false;
return (
(!start || row.period_end >= start) &&
(!end || row.period_start <= end)
);
});
}
export default function InvestmentMapIndicators({
context,
theme,
}: {
context: QuantitativeMapContext | null;
theme: string;
}) {
const filterId = useId();
const [indicator, setIndicator] = useState('all');
const [start, setStart] = useState('');
const [end, setEnd] = useState('');
if (!['all', 'workforce', 'economic_potential'].includes(theme))
return null;
const themeRows =
context?.records.filter(
(row) => theme === 'all' || row.theme === theme,
) ?? [];
const rows = filterMapIndicators(themeRows, indicator, start, end);
const invalidPeriod = Boolean(start && end && start > end);
const filtered = indicator !== 'all' || Boolean(start || end);
return (
<section
className="space-y-2 rounded border p-3"
aria-label="Indicadores quantitativos do contexto territorial"
>
<h3 className="font-semibold">
Indicadores quantitativos do contexto territorial
</h3>
<p>
Observações públicas das zonas e parques devolvidos como
contexto territorial, independentes das evidências das
oportunidades. Emprego não mede trabalhadores disponíveis;
investimento, produção e exportações não constituem um índice de
potencial ou garantia de retorno. A natureza prevista ou
efectiva de cada valor depende da descrição da fonte, não é
determinada automaticamente.
</p>
{!context ? (
<p role="status">A carregar indicadores quantitativos…</p>
) : context.state === 'error' ? (
<p role="alert">
Não foi possível consultar os indicadores. Pode repetir a
consulta do mapa.
</p>
) : context.state === 'not_included' ? (
<p>
O conjunto de zonas não está incluído com estes filtros
específicos de oportunidades. Não foi feita uma medição
quantitativa neste âmbito.
</p>
) : context.state === 'unavailable' ? (
<p>
Observações quantitativas indisponíveis nesta versão do
cadastro; os restantes dados do mapa mantêm-se.
</p>
) : (
<>
<fieldset className="grid gap-3 sm:grid-cols-3">
<legend>
Filtrar observações do contexto territorial
</legend>
<label
className="grid gap-1"
htmlFor={`${filterId}-indicator`}
>
Indicador
<select
className="bg-background w-full rounded border p-2"
id={`${filterId}-indicator`}
value={indicator}
onChange={(event) =>
setIndicator(event.target.value)
}
>
<option value="all">
Todos os indicadores
</option>
{Object.entries(labels).map(([key, label]) => (
<option key={key} value={key}>
{label}
</option>
))}
</select>
</label>
<label
className="grid gap-1"
htmlFor={`${filterId}-start`}
>
Período de referência — desde
<input
className="w-full min-w-0 rounded border p-2"
id={`${filterId}-start`}
type="date"
value={start}
onInput={(event) =>
setStart(event.currentTarget.value)
}
aria-invalid={invalidPeriod}
aria-describedby={`${filterId}-help`}
/>
</label>
<label
className="grid gap-1"
htmlFor={`${filterId}-end`}
>
Período de referência — até
<input
className="w-full min-w-0 rounded border p-2"
id={`${filterId}-end`}
type="date"
value={end}
onInput={(event) =>
setEnd(event.currentTarget.value)
}
aria-invalid={invalidPeriod}
aria-describedby={`${filterId}-help`}
/>
</label>
<button
className="w-fit rounded border px-3 py-2 text-sm underline sm:col-span-3"
type="button"
onClick={() => {
setIndicator('all');
setStart('');
setEnd('');
}}
>
Limpar filtros dos indicadores
</button>
</fieldset>
<p id={`${filterId}-help`}>
O período selecciona observações com sobreposição de
datas, incluindo os limites. Datas incompletas ou
invertidas na fonte ficam fora do filtro temporal. A
data da fonte não substitui o período de referência.
Estes filtros actuam apenas nas observações recebidas;
não alteram oportunidades ou limites de cobertura.
</p>
{invalidPeriod && (
<p role="alert">
O início do período deve ser anterior ou igual ao
fim.
</p>
)}
<p role="status">
{rows.length}{' '}
{rows.length === 1 ? 'observação' : 'observações'} nesta
camada, de {themeRows.length} recebidas, em{' '}
{context.zones_consulted}{' '}
{context.zones_consulted === 1
? 'ficha territorial consultada.'
: 'fichas territoriais consultadas.'}{' '}
A contagem não mede cobertura nacional.
</p>
{context.truncated && (
<p role="note">
Cobertura incompleta: consulta limitada a{' '}
{context.zone_limit} fichas e {context.record_limit}{' '}
observações. Refine a localização.
</p>
)}
{rows.length === 0 ? (
<p>
{filtered
? 'Nenhuma observação corresponde aos filtros dos indicadores.'
: 'Sem observações públicas destes indicadores na resposta.'}{' '}
Valor desconhecido não equivale a zero.
</p>
) : (
<details>
<summary>
Consultar valores, unidades e fontes (
{rows.length})
</summary>
<div className="overflow-x-auto">
<table className="w-full text-left text-sm">
<caption>
Cada linha conserva o âmbito da fonte.
Sem somas, conversões de unidade ou
moeda, médias ou comparação automática
entre períodos e populações.
</caption>
<thead>
<tr>
<th scope="col">
Ficha e indicador
</th>
<th scope="col">Valor e unidade</th>
<th scope="col">Período e base</th>
<th scope="col">
Fonte e metodologia
</th>
</tr>
</thead>
<tbody>
{rows.map((row) => (
<tr
key={`${row.zone_id}:${row.id}`}
className="border-t align-top"
>
<th scope="row" className="p-2">
<a
className="underline"
href={row.url}
>
{row.zone_name}
</a>
<p>
{labels[
row.indicator
] ?? row.indicator}
: {text(row.label)}
</p>
<p>
{text(row.description)}
</p>
<p>
Versão pública{' '}
{row.zone_version}
</p>
</th>
<td className="p-2">
<p>
{row.value ??
'Por determinar'}{' '}
· {text(row.unit)}
</p>
<p>
{row.knowledge ===
'documented'
? 'Documentado na fonte'
: row.knowledge ===
'disputed'
? 'Em divergência'
: 'Desconhecido'}
</p>
</td>
<td className="p-2">
<p>
{text(row.period_start)}{' '}
a {text(row.period_end)}
</p>
<p>
Âmbito:{' '}
{text(row.scope)}
</p>
<p>
População/base:{' '}
{text(row.population)}
</p>
</td>
<td className="p-2">
<p>
Fonte:{' '}
{text(
row.source_reference,
)}{' '}
· Data:{' '}
{text(row.source_date)}
</p>
<p>
Metodologia:{' '}
{text(row.methodology)}
</p>
{row.missing_metadata
.length > 0 && (
<p role="note">
Informação em falta:{' '}
{row.missing_metadata
.map(
(key) =>
metadataLabels[
key
] ??
key,
)
.join(', ')}
. Não agregar ou
comparar como se
estivesse completa.
</p>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
</details>
)}
</>
)}
</section>
);
}
+9 -1
View File
@@ -6,6 +6,7 @@ import InputError from '@/components/input-error';
import { Button } from '@/components/ui/button';
import { Separator } from '@/components/ui/separator';
import { Spinner } from '@/components/ui/spinner';
import { cn } from '@/lib/utils';
type Props = {
routes?: {
@@ -15,6 +16,7 @@ type Props = {
label?: string;
loadingLabel?: string;
separator?: string;
separatorClassName?: string;
};
export default function PasskeyVerify({
@@ -22,6 +24,7 @@ export default function PasskeyVerify({
label,
loadingLabel,
separator,
separatorClassName,
}: Props = {}) {
const { verify, isLoading, error, isSupported } = usePasskeyVerify({
...(routes && {
@@ -64,7 +67,12 @@ export default function PasskeyVerify({
<Separator className="w-full" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background text-muted-foreground px-2">
<span
className={cn(
'bg-background text-muted-foreground px-2',
separatorClassName,
)}
>
{separator ?? 'Ou continuar com email'}
</span>
</div>
+3 -5
View File
@@ -3,6 +3,7 @@ import type { ComponentProps, Ref } from 'react';
import { useState } from 'react';
import { Input } from '@/components/ui/input';
import { cn } from '@/lib/utils';
import { useTempoUi } from '@/components/tempo-brand';
export default function PasswordInput({
className,
@@ -10,6 +11,7 @@ export default function PasswordInput({
...props
}: Omit<ComponentProps<'input'>, 'type'> & { ref?: Ref<HTMLInputElement> }) {
const [showPassword, setShowPassword] = useState(false);
const { t } = useTempoUi();
return (
<div className="relative">
@@ -23,11 +25,7 @@ export default function PasswordInput({
type="button"
onClick={() => setShowPassword((prev) => !prev)}
className="text-muted-foreground hover:text-foreground focus-visible:ring-ring absolute inset-y-0 right-0 flex items-center rounded-r-md px-3 focus-visible:ring-[3px] focus-visible:outline-none"
aria-label={
showPassword
? 'Ocultar palavra-passe'
: 'Mostrar palavra-passe'
}
aria-label={showPassword ? 'Ocultar palavra-passe' : 'Mostrar palavra-passe'}
tabIndex={-1}
>
{showPassword ? (
@@ -131,9 +131,14 @@ export function PortalGroupedMenu() {
);
const accountGroups = groups.filter(
(group) =>
!['home', 'discovery', 'realization', 'support', 'work'].includes(
group.id,
),
![
'home',
'procurement-area',
'discovery',
'realization',
'support',
'work',
].includes(group.id),
);
const institutionalCurrent = url.split('?')[0] === '/contacto';
return (
@@ -198,7 +203,6 @@ export function PortalGroupedMenu() {
'discovery',
'realization',
'support',
'work',
].includes(group.id),
)
.map((group) => (
@@ -254,7 +258,9 @@ export function PortalGroupedMenu() {
<DropdownMenuTrigger
className="tempo-nav-trigger"
data-current-group={
institutionalCurrent || undefined
institutionalCurrent ||
current === 'work' ||
undefined
}
>
Mais
@@ -304,6 +310,17 @@ export function PortalGroupedMenu() {
</DropdownMenuItem>
),
)}
{groups
.filter((group) => group.id === 'work')
.map((group) => (
<Fragment key={group.id}>
<DropdownMenuSeparator />
<DropdownMenuLabel>
{t(group.label)}
</DropdownMenuLabel>
{items(group.children ?? [])}
</Fragment>
))}
</DropdownMenuContent>
</DropdownMenu>
</li>
+9 -5
View File
@@ -226,14 +226,14 @@ export default function PortalHero({
</p>
<form
action="/oportunidades"
action="/pesquisa"
method="get"
onSubmit={(event) => {
event.preventDefault();
const value = new FormData(event.currentTarget).get(
'q',
);
router.get('/oportunidades', {
router.get('/pesquisa', {
q:
typeof value === 'string'
? value.trim()
@@ -241,14 +241,14 @@ export default function PortalHero({
});
}}
role="search"
aria-label="Pesquisar oportunidades"
aria-label="Pesquisar no InvesteMoz"
className="mt-7 w-full max-w-4xl"
>
<label
htmlFor="portal-opportunity-search"
className="mb-2 block text-left text-sm font-medium text-white/90"
>
Pesquisa rápida de oportunidades
Pesquisa rápida no InvesteMoz
</label>
<div className="flex min-w-0 flex-col gap-2 rounded-2xl border border-white/30 bg-white p-1.5 shadow-2xl shadow-black/20 focus-within:ring-2 focus-within:ring-white focus-within:ring-offset-4 focus-within:ring-offset-[#101820] min-[400px]:flex-row">
<input
@@ -259,7 +259,7 @@ export default function PortalHero({
aria-describedby="portal-opportunity-search-help"
value={query}
onChange={(event) => setQuery(event.target.value)}
placeholder="Ex.: agronegócio, Nampula, energia..."
placeholder="Ex.: agronegócio, Nampula, legislação..."
className="h-12 w-full min-w-0 flex-1 rounded-xl border-0 bg-white px-4 text-base text-slate-900 outline-none placeholder:text-slate-500"
/>
<button
@@ -335,6 +335,10 @@ export default function PortalHero({
</div>
))}
</dl>
<p className="mt-3 max-w-lg text-xs text-white/80">
Províncias com localização declarada nas oportunidades
públicas. Cada oportunidade conta uma vez no total.
</p>
</div>
</section>
);
+9
View File
@@ -0,0 +1,9 @@
import { createContext, useContext } from 'react';
import { PortalGroupedMenu } from '@/components/portal-grouped-menu';
export const MenuPlacement = createContext(false);
export function PortalMenu({ primary = false }: { primary?: boolean }) {
const placed = useContext(MenuPlacement);
return placed && !primary ? null : <PortalGroupedMenu />;
}
@@ -399,6 +399,8 @@ export function ProactiveInvestmentEditor({
infrastructure: 'Infra-estrutura',
supply: 'Oferta',
demand: 'Procura',
workforce:
'Mão-de-obra — evidência documentada',
},
(v) => change({ category: v }),
)}
+3
View File
@@ -1,8 +1,10 @@
import SectorResourceReference, { type SectorResourceData } from './sector-resource-reference';
import { Link } from '@inertiajs/react';
import EducationalArticleCard, {
type EducationalArticle,
} from './educational-article-card';
export type SectorEntryData = {
resource_reference?: SectorResourceData | null;
articles: EducationalArticle[];
relations: {
id: string;
@@ -43,6 +45,7 @@ export default function SectorEntry({
modalidade adequada. Cada oportunidade tem promotor, objetivo e
dados próprios.
</p>
{data.resource_reference && <SectorResourceReference data={data.resource_reference} />}
<nav aria-label="Ações no setor" className="flex flex-wrap gap-3">
{data.start_href ? (
<Link
@@ -0,0 +1,233 @@
import { useState } from "react";
import { Link, usePage } from "@inertiajs/react";
export type SectorResourceArea = {
id: string;
group_ids: string[];
title: string;
locations_text: string;
location_labels: string[];
market_evidence: string;
value_chain: string;
indicative_location: string;
promotion_ready: boolean;
source_row: string;
};
export type SectorResourceSelection = {
sector_slug: string;
key: string;
group_id?: string | null;
group_name?: string | null;
area_id: string;
title: string;
location_label: string | null;
locations_text: string;
value_chain: string;
source_sha256: string;
reference_date: string;
href: string;
};
export type SectorResourceData = {
sector_slug: string;
title: string;
introduction: string;
group_label: string;
resource_heading: string;
resource_notice: string;
background: {title:string;paragraphs:string[]}[];
source_sha256: string;
reference_date: string;
groups: { id: string; name: string; locations_text: string; location_labels: string[] }[];
areas: SectorResourceArea[];
projects: string[];
indicators: { label: string; value: string; source: string }[];
risks: string[];
sources: string[];
source_links?: {title:string;url:string}[];
};
export default function SectorResourceReference({ data }: { data: SectorResourceData }) {
const url = usePage().url,
query = new URL(url, "https://investmoz.invalid").searchParams;
const [location, setLocation] = useState(query.get("resource_location") ?? ""),
[search, setSearch] = useState(""),
[area, setArea] = useState(query.get("resource_area") ?? ""),
[group, setGroup] = useState(query.get("resource_group") ?? "");
const locations = [
...new Set(
data.groups
.flatMap((m) => m.location_labels)
.concat(data.areas.flatMap((m) => m.location_labels)),
),
].sort((a, b) => a.localeCompare(b, "pt"));
const matches = (x: { location_labels: string[] }, text: string) =>
(!location || x.location_labels.includes(location)) &&
(!search || text.toLocaleLowerCase("pt").includes(search.toLocaleLowerCase("pt")));
const groups = data.groups.filter(
(m) => (!group || m.id === group) && matches(m, m.name + " " + m.locations_text),
),
areas = data.areas.filter(
(a) =>
(!area || a.id === area) &&
(!group || a.group_ids.includes(group)) &&
matches(a, a.title + " " + a.value_chain + " " + a.locations_text),
);
return (
<section
aria-label={data.title}
className="space-y-5 rounded border p-4"
>
<h2 className="text-xl font-semibold">{data.title}</h2>
<p>{data.introduction}</p>
<div className="flex flex-wrap gap-3">
<label>
Localização
<select
className="mt-1 block max-w-full rounded border p-2"
aria-label="Localização sectorial"
value={location}
onChange={(e) => {
setLocation(e.target.value);
setArea("");
}}
>
<option value="">Todas as localizações</option>
{locations.map((p) => (
<option key={p} value={p}>
{p === "Maputo" ? "Maputo — província/cidade a precisar" : p}
</option>
))}
</select>
</label>
<label>
{data.group_label}
<select
className="mt-1 block max-w-full rounded border p-2"
aria-label={data.group_label}
value={group}
onChange={(e) => {
setGroup(e.target.value);
setArea("");
}}
>
<option value="">Todos os grupos</option>
{data.groups.map((m) => (
<option key={m.id} value={m.id}>
{m.name}
</option>
))}
</select>
</label>
<label>
Recurso, local ou actividade
<input
className="mt-1 block max-w-full rounded border p-2"
aria-label="Pesquisar recursos e cadeia de valor"
value={search}
onChange={(e) => {
setSearch(e.target.value);
setArea("");
}}
/>
</label>
{area && (
<button type="button" onClick={() => setArea("")}>
Ver todas as áreas
</button>
)}
</div>
<div>
<h3 className="font-semibold">{data.resource_heading}</h3>
{groups.length ? (
<ul className="space-y-3">
{groups.map((m) => (
<li key={m.id}>
<strong>{m.name}</strong>
<p>{m.locations_text}</p>
</li>
))}
</ul>
) : (
<p>Sem referências para esta pesquisa.</p>
)}
<p className="text-sm">{data.resource_notice}</p>
</div>
<div>
<h3 className="font-semibold">Possibilidades na cadeia de valor</h3>
{areas.map((a) => (
<article key={a.id} className="my-3 space-y-2 rounded border p-3">
<h4 className="font-semibold">{a.title}</h4>
<p>{a.locations_text}</p>
<p>{a.value_chain.replace(/\s*\(indicativo\)/, "")}</p>
<details>
<summary>Evidência e âmbito territorial</summary>
<p>{a.market_evidence}</p>
{a.indicative_location !== a.locations_text && <p>{a.indicative_location}</p>}
<p>Referência de {data.reference_date}; preparação da actividade sujeita ao investimento concreto.</p>
</details>
<Link
className="underline"
href={
"/investimentos/a-partir-do-setor/" + data.sector_slug + "?" +
new URLSearchParams({
resource_area: a.id,
resource_source: data.source_sha256,
...(group ? { resource_group: group } : {}),
...(location ? { resource_location: location } : {}),
})
}
>
Preparar uma ideia nesta cadeia de valor
</Link>
</article>
))}
{!areas.length && <p>Sem áreas de investimento para esta selecção.</p>}
</div>
<details>
<summary>Projectos de referência</summary>
<ul>
{data.projects.map((p, i) => (
<li key={i}>{p}</li>
))}
</ul>
</details>
{!!data.indicators.length && (
<details>
<summary>Produção e recursos por período</summary>
{data.indicators.map((s, i) => (
<article key={i}>
<h4>{s.label}</h4>
<p>
{s.value.replace(
"Não encontrei dados de produção das outras gemas.",
"Outras gemas: sem valores apresentados.",
)}
</p>
<p>Fonte: {s.source}</p>
</article>
))}
<p>
Produção, capacidade, recursos e previsões são grandezas distintas. Os valores
conservam os períodos e divergências das fontes.
</p>
</details>
)}
<details>
<summary>Riscos a considerar</summary>
<ul>
{data.risks.map((r, i) => (
<li key={i}>{r}</li>
))}
</ul>
</details>
{data.background.map(section=><details key={section.title}><summary>{section.title}</summary>{section.paragraphs.map((text,i)=><p key={i}>{text}</p>)}{section.title.startsWith("Enquadramento")&&<p>Referência documental; não activa benefícios fiscais, licenças ou direitos no portal.</p>}</details>)}
<details>
<summary>Fontes e data</summary>
<p>Referências reunidas em {data.reference_date}.</p>
<ul>
{data.sources.map((s, i) => (
<li key={i}>{data.source_links?.find(link=>link.title===s)?.url ? <a className="underline" href={data.source_links.find(link=>link.title===s)!.url} target="_blank" rel="noopener noreferrer">{s}</a> : s}</li>
))}
</ul>
</details>
</section>
);
}
@@ -1,3 +1,6 @@
import {SimulationIrpcPecObligation} from './simulation-irpc-pec-obligation';
import { SimulationIrpcPpcObligation } from './simulation-irpc-ppc-obligation';
import { SimulationIrpcInvestment } from './simulation-irpc-investment';
import { SimulationIrpcEconomic } from './simulation-irpc-economic';
import { FiscalProofParcelFields } from './fiscal-proof-parcel';
import { SimulationIrpcCarry } from './simulation-irpc-carry';
@@ -339,6 +342,9 @@ export function SimulationIrpcAnnual({ draft, onChange }: Props) {
</fieldset>
<fieldset className="space-y-2 rounded border p-3">
<legend>PPC efectivamente pagos</legend>
<p>Simulação fiscal para avaliar a viabilidade do negócio e as necessidades de tesouraria. Os valores e meses previstos são estimativas; o portal não executa pagamentos nem submete declarações fiscais.</p>
<SimulationIrpcPecObligation book={b} onChange={pecObligation=>patch({pecObligation})}/>
<SimulationIrpcPpcObligation book={b} onChange={ppcObligation=>patch({ppcObligation})}/>
{group('ppc', 'PPC efectuados')}
<p>
Regista comprovativos de pagamentos por conta; não
@@ -479,6 +485,7 @@ export function SimulationIrpcAnnual({ draft, onChange }: Props) {
}
/>
<SimulationIrpcInternational book={b} save={save} />
<SimulationIrpcInvestment draft={draft} book={b} save={save} />
<SimulationIrpcPec book={b} save={save} />
<SimulationIrpcCarry book={b} save={save} />
{[
@@ -0,0 +1,150 @@
import { useState } from 'react';
import { annualBook } from '@/lib/irpc-annual-settlement';
import type { NormalBook } from '@/lib/irpc-normal';
import {
prepareEconomicInclusion,
calculateEconomicInclusions,
economicInclusionMoney,
} from '@/lib/irpc-normal-economic';
export function SimulationIrpcEconomicInclusion({
draft,
book,
save,
}: {
draft: Record<string, unknown>;
book: NormalBook;
save: (b: NormalBook) => void;
}) {
const [selected, setSelected] = useState('');
const [profit, setProfit] = useState('');
const [credit, setCredit] = useState('');
const [reference, setReference] = useState('');
const [error, setError] = useState('');
const [preview, setPreview] = useState<{
fingerprint: string;
book: NormalBook;
net: string;
} | null>(null);
const sources = annualBook(draft)?.economicCredits?.rows ?? [];
if (!sources.length) return null;
const fingerprint = JSON.stringify([
book,
sources,
selected,
profit,
credit,
reference,
]);
return (
<section
className="space-y-3 rounded border p-3"
aria-label="Incluir ou corrigir lucros e crédito económico em L6"
>
<h3 className="font-semibold">
Incluir ou corrigir lucros e crédito económico em L6
</h3>
<p>
Escolha a origem preparada em L8. Indique os montantes já
considerados na contabilidade e noutros ajustamentos, antes
deste mapa; zero deve ser indicado expressamente. A prévia
propõe apenas a diferença e conserva o resultado anterior.
Depois de confirmar, calcule e guarde L6 pelo percurso
existente; L8 exige nova conferência da colecta. Valores
derivados mantêm as fracções exactas, sem arredondamento fiscal
presumido.
</p>
<label className="block">
Origem dos lucros
<select
value={selected}
onChange={(e) => setSelected(e.target.value)}
>
<option value="">Escolha a origem</option>
{sources.map((r) => (
<option key={r.id} value={r.id}>
{r.distributionDocument} — {r.distributionParcel}
</option>
))}
</select>
</label>
<label className="block">
Rendimentos já considerados — MZN (incluindo eventual mais-valia
da partilha)
<input
value={profit}
onChange={(e) => setProfit(e.target.value)}
/>
</label>
<label className="block">
Crédito nominal já acrescido — MZN
<input
value={credit}
onChange={(e) => setCredit(e.target.value)}
/>
</label>
<label className="block">
Suporte da reconciliação
<textarea
value={reference}
onChange={(e) => setReference(e.target.value)}
/>
</label>
{error && <p role="alert">{error}</p>}
<button
type="button"
onClick={() => {
try {
const source = sources.find((r) => r.id === selected);
if (!source)
throw Error('Escolha explicitamente uma origem.');
const next = prepareEconomicInclusion(book, {
source,
profitAlreadyIncluded: profit,
creditAlreadyIncluded: credit,
reference,
});
const model = calculateEconomicInclusions(
next.economicInclusions!,
next.base!.subjectNuit,
next.year,
);
setPreview({
fingerprint,
book: next,
net: economicInclusionMoney(model.net),
});
setError('');
} catch (e) {
setPreview(null);
setError(String(e));
}
}}
>
Pré-visualizar inclusão/correcção
</button>
{preview && preview.fingerprint === fingerprint && (
<div className="space-y-2 rounded border p-3">
<p>
Efeito total deste mapa no lucro tributável:{' '}
{preview.net} MZN. Substitui as parcelas da mesma
origem, sem as acumular.
</p>
<button
type="button"
onClick={() => {
if (preview.fingerprint !== fingerprint) return;
save(preview.book);
setPreview(null);
}}
>
Confirmar preparação em L6
</button>
<button type="button" onClick={() => setPreview(null)}>
Cancelar prévia
</button>
</div>
)}
</section>
);
}
@@ -110,7 +110,10 @@ export function SimulationIrpcEconomic({
rowChange({ ...r, ...v });
const prefix = 'Crédito económico ' + (i + 1) + ' — ';
const field = (
key: Exclude<keyof EconomicPosition, 'previousUse'>,
key: Exclude<
keyof EconomicPosition,
'previousUse' | 'liquidation' | 'association'
>,
label: string,
) => (
<label key={key} className="block">
@@ -145,9 +148,7 @@ export function SimulationIrpcEconomic({
</select>
</label>
);
const outside =
(!!r.kind && r.kind !== 'ordinary_profit') ||
r.article40 === 'yes';
const outside = r.article40 === 'yes';
const detail = (
<>
{(
@@ -226,7 +227,60 @@ export function SimulationIrpcEconomic({
'Suporte da quota proporcional da colecta',
],
] as const
).map(([key, label]) => field(key, label))}
)
.filter(
([key]) =>
r.nationalMethod !== 'proportional' ||
(key !== 'nationalLimit' &&
key !== 'nationalLimitReference'),
)
.map(([key, label]) => field(key, label))}
<label className="block">
Quota proporcional
<select
aria-label={prefix + 'Método da quota'}
value={r.nationalMethod ?? 'documented'}
onChange={(e) =>
patch({
nationalMethod: e.target
.value as EconomicPosition['nationalMethod'],
})
}
>
<option value="documented">
Limite documentado
</option>
<option value="proportional">
Derivar da colecta L6 ordinária
</option>
</select>
</label>
{r.nationalMethod === 'proportional' && (
<label className="block">
Correspondência dos lucros mais acréscimo53 na
base corrente
<textarea
aria-label={
prefix + 'Correspondência da quota'
}
value={r.nationalBasisReference ?? ''}
onChange={(e) =>
patch({
nationalBasisReference:
e.target.value,
})
}
/>
<span className="block">
Quota = colecta L6 × (lucros incluídos +
crédito nominal acrescido) / matéria
colectável L6. Não altera a base nem
acrescenta o crédito novamente. O limite
manual anterior fica conservado, mas não é
usado neste modo.
</span>
</label>
)}
{choice(
'incomeIncluded',
'Lucros ilíquidos e acréscimo incluídos na base corrente',
@@ -339,6 +393,155 @@ export function SimulationIrpcEconomic({
</option>
</select>
</label>
{r.kind === 'association' && (
<fieldset className="space-y-2 rounded border p-3">
<legend>Associação — art.64(4)</legend>
<label className="block">
Modalidade
<select
aria-label={
prefix + 'Modalidade da associação'
}
value={r.association?.kind ?? ''}
onChange={(e) =>
patch({
association: {
kind: e.target.value as
| ''
| 'participation'
| 'quota',
effectivelyTaxed:
r.association
?.effectivelyTaxed ??
'',
reference:
r.association
?.reference ?? '',
},
})
}
>
<option value="">
Escolha a modalidade
</option>
<option value="participation">
Em participação
</option>
<option value="quota">À quota</option>
</select>
</label>
{r.association?.kind === 'participation' && (
<label className="block">
Rendimentos distribuídos efectivamente
tributados
<select
aria-label={
prefix +
'Tributação efectiva da participação'
}
value={
r.association.effectivelyTaxed
}
onChange={(e) =>
patch({
association: {
...r.association!,
effectivelyTaxed: e
.target.value as
| ''
| 'yes'
| 'no',
},
})
}
>
<option value="">
Informação em falta
</option>
<option value="yes">
Sim — documentado
</option>
<option value="no">
Não — documentado
</option>
</select>
</label>
)}
<label className="block">
Suporte do contrato, rendimentos e adaptação
do crédito
<input
aria-label={
prefix + 'Suporte da associação'
}
value={r.association?.reference ?? ''}
onChange={(e) =>
patch({
association: {
kind: '',
effectivelyTaxed: '',
...r.association,
reference: e.target.value,
},
})
}
/>
</label>
</fieldset>
)}
{r.kind === 'liquidation' && (
<fieldset className="space-y-2 rounded border p-3">
<legend>
Partilha em liquidação — art.64(3)
</legend>
<p>
No rendimento abaixo indique a diferença
positiva classificada documentalmente como
aplicação de capitais. O crédito limita-se
também ao valor atribuído menos entradas
efectivas para capital. O excesso mantém
tratamento próprio de mais-valia; não
integra a quota económica. Confirme a
inclusão L6 e o IRPC correspondente apenas à
parcela elegível.
</p>
{(
[
[
'attributed',
'Valor atribuído na partilha — MZN',
],
[
'paidInCapital',
'Entradas efectivas para capital na contabilidade da liquidada — MZN',
],
[
'reference',
'Suporte da partilha e classificação do rendimento',
],
] as const
).map(([key, label]) => (
<label key={key} className="block">
{label}
<input
aria-label={prefix + label}
value={r.liquidation?.[key] ?? ''}
onChange={(e) =>
patch({
liquidation: {
attributed: '',
paidInCapital: '',
reference: '',
...r.liquidation,
[key]: e.target.value,
},
})
}
/>
</label>
))}
</fieldset>
)}
{residentConfirmed && r.recipientResident === 'yes' ? (
<p>
Residência do beneficiário reutilizada da
@@ -1,3 +1,4 @@
import { SimulationIrpcPtCdt } from './simulation-irpc-pt-cdt';
import { FiscalProofParcelFields } from './fiscal-proof-parcel';
import CountrySelect, { type Country } from '@/components/country-select';
import { countryIsoCodes } from '@/lib/country-iso-codes';
@@ -116,6 +117,15 @@ export function SimulationIrpcInternational({
<fieldset className="space-y-3 rounded border p-3">
<legend>Crédito por dupla tributação internacional</legend>
<p>{internationalCreditSource}</p>
{c.positions.some((p) => p.nationalMethod === 'proportional') && (
<p>
No modo de derivação ordinária abaixo, o portal calcula a
fracção nacional a partir da colecta e matéria colectável L6
correntes e da parcela líquida correspondida. Mantêm-se as
restantes condições e limites do modo documentado; não
determina a convenção aplicável.
</p>
)}
<p>
Reúna os comprovativos de cada país numa posição por exercício.
Indique a fracção nacional e a convenção conforme o suporte do
@@ -220,6 +230,59 @@ export function SimulationIrpcInternational({
onChange={(country) => patch({ country })}
allowLegacyValue
/>
<label className="block">
Fracção nacional
<select
aria-label={
'Internacional ' +
(index + 1) +
' — Método da fracção nacional'
}
value={p.nationalMethod ?? 'documented'}
onChange={(e) =>
patch({
nationalMethod: e.target
.value as InternationalPosition['nationalMethod'],
})
}
>
<option value="documented">
Limite documentado
</option>
<option value="proportional">
Derivar da colecta L6 ordinária
</option>
</select>
</label>
{p.nationalMethod === 'proportional' && (
<label className="block">
Correspondência da parcela líquida estrangeira
<textarea
aria-label={
'Internacional ' +
(index + 1) +
' — Correspondência da parcela líquida'
}
value={p.nationalBasisReference ?? ''}
onChange={(e) =>
patch({
nationalBasisReference:
e.target.value,
})
}
/>
<span className="block">
Indique abaixo a parcela líquida já incluída
na matéria colectável, após os custos,
perdas e deduções correspondentes. A fracção
será colecta L6 × parcela líquida / matéria
colectável L6, antes de créditos. O portal
não reparte custos ou perdas nem determina
convenções. O limite manual anterior é
conservado, mas não usado neste modo.
</span>
</label>
)}
{(
[
['subjectNuit', 'NUIT titular'],
@@ -238,23 +301,33 @@ export function SimulationIrpcInternational({
'Suporte da fracção nacional e exercício',
],
] as const
).map(([key, label]) => (
<label key={key} className="block">
{label}
<input
aria-label={
'Internacional ' +
(index + 1) +
' — ' +
label
}
value={p[key]}
onChange={(e) =>
patch({ [key]: e.target.value })
}
/>
</label>
))}
)
.filter(
([key]) =>
p.nationalMethod !== 'proportional' ||
(key !== 'nationalLimit' &&
key !== 'nationalLimitReference'),
)
.map(([key, label]) => (
<label key={key} className="block">
{key === 'incomeAmount' &&
p.nationalMethod === 'proportional'
? 'Parcela líquida estrangeira incluída — MZN'
: label}
<input
aria-label={
'Internacional ' +
(index + 1) +
' — ' +
label
}
value={p[key]}
onChange={(e) =>
patch({ [key]: e.target.value })
}
/>
</label>
))}
<label className="block">
Rendimento incluído na matéria colectável
<select
@@ -315,23 +388,65 @@ export function SimulationIrpcInternational({
</option>
</select>
</label>
{(p.treaty === 'yes' || p.treatyLimit) && (
{p.treaty === 'yes' && (
<label className="block">
Imposto pago elegível nos termos da convenção —
limite MZN
<input
Determinar limite da convenção
<select
aria-label={
'Internacional ' +
(index + 1) +
' — Limite CDT'
' — Método CDT'
}
value={p.treatyLimit}
value={p.treatyMethod ?? 'documented'}
onChange={(e) =>
patch({ treatyLimit: e.target.value })
patch({
treatyMethod: e.target
.value as InternationalPosition['treatyMethod'],
})
}
/>
>
<option value="documented">
Limite documentado
</option>
<option value="ptMzRoyalty">
Portugal — royalties ordinárias,
art.12/23(3)a
</option>
<option value="ptMzDividend">
Portugal — dividendos, art.10/23(3)a
</option>
<option value="ptMzInterest">
Portugal — juros ordinários,
art.11/23(3)a
</option>
</select>
</label>
)}
<SimulationIrpcPtCdt
position={p}
patch={patch}
prefix={'Internacional ' + (index + 1) + ' — '}
/>
{(p.treatyMethod ?? 'documented') === 'documented' &&
(p.treaty === 'yes' || p.treatyLimit) && (
<label className="block">
Imposto pago elegível nos termos da
convenção — limite MZN
<input
aria-label={
'Internacional ' +
(index + 1) +
' — Limite CDT'
}
value={p.treatyLimit}
onChange={(e) =>
patch({
treatyLimit: e.target.value,
})
}
/>
</label>
)}
<label className="block">
Suporte da convenção ou da não aplicação
<input
@@ -0,0 +1,658 @@
import { QualificationFields } from './simulation-irpc-special';
import { FiscalProofParcelFields } from './fiscal-proof-parcel';
import {
prepareInvestmentCredit,
prepareInvestmentCarry,
type AnnualBook,
} from '@/lib/irpc-annual-settlement';
import {
investmentCreditSource,
newInvestmentAsset,
type InvestmentCredit,
type InvestmentAsset,
} from '@/lib/irpc-investment-credit';
export function SimulationIrpcInvestment({
draft,
book,
save,
}: {
draft: Record<string, unknown>;
book: AnnualBook;
save: (b: AnnualBook) => void;
}) {
const c = book.investmentCredit;
if (!c)
return (
<fieldset className="rounded border p-3">
<legend>Crédito fiscal por investimento — CBF15</legend>
<p>
Prepare o detalhe do investimento efectivo e da qualificação
do projecto. Substitui neste cálculo o resumo anterior dos
benefícios, que fica conservado. O recorte cobre
investimento efectivo de2026 dentro dos cinco exercícios do
prazo,com colecta integral ou quota residual documental do
projecto, sem outros benefícios próprios ou transportes
anteriores.
</p>
<button
type="button"
onClick={() => save(prepareInvestmentCredit(draft, book))}
>
Preparar crédito por investimento
</button>
</fieldset>
);
const patch = (v: Partial<InvestmentCredit>) =>
save({ ...book, investmentCredit: { ...c, ...v } });
const text = (
key:
| 'projectReference'
| 'locationReference'
| 'startDate'
| 'startReference'
| 'recognitionReference'
| 'noOtherBenefitsReference'
| 'soleProjectReference',
label: string,
) => (
<label className="block">
{label}
<input
aria-label={'CBF15 — ' + label}
value={c[key]}
onChange={(e) => patch({ [key]: e.target.value })}
/>
</label>
);
const choice = (
key:
| 'cbfRequirements'
| 'genericOnly'
| 'noOtherBenefits'
| 'soleProject',
label: string,
) => (
<label className="block">
{label}
<select
aria-label={'CBF15 — ' + label}
value={c[key]}
onChange={(e) => patch({ [key]: e.target.value })}
>
<option value="">Informação em falta</option>
<option value="yes">Sim — documentado</option>
<option value="no">Não — documentado</option>
</select>
</label>
);
return (
<fieldset className="space-y-3 rounded border p-3">
<legend>Crédito fiscal por investimento — CBF15</legend>
<p>{investmentCreditSource}</p>
<p>
Beneficiário {c.subjectNuit}, exercício {c.year}. Resumo
anterior conservado:{' '}
{book.internationalCredits?.benefits.state ||
'informação em falta'}
; montante{' '}
{book.internationalCredits?.benefits.amount || 'não preenchido'}
. O detalhe aplica apenas o benefício aqui qualificado; não
volta a deduzir o resumo.
</p>
<button
type="button"
onClick={() =>
patch({
subjectNuit: book.subjectNuit,
year: book.year,
assessmentKey: book.assessmentKey,
})
}
>
Vincular CBF15 à colecta seleccionada
</button>
<QualificationFields
label="CBF15"
value={c.qualification}
onChange={(qualification) => patch({ qualification })}
/>
{choice(
'noOtherBenefits',
'Sem outros benefícios próprios/cumulações neste recorte',
)}
{text(
'noOtherBenefitsReference',
'Reconciliação dos outros benefícios e tratamentos L6/L7',
)}
{c.qualification.state === 'known' && (
<>
{text(
'projectReference',
'Despacho/termos ou instrumento legal do projecto',
)}
{choice(
'cbfRequirements',
'Condições CBF3/9 e âmbito do projecto documentados',
)}
{choice(
'genericOnly',
'Investimento no regime genérico, fora dos benefícios específicos — art.13',
)}
<label className="block">
Reconhecimento — art.10
<select
aria-label="CBF15 — Reconhecimento"
value={c.recognition}
onChange={(e) =>
patch({
recognition: e.target
.value as InvestmentCredit['recognition'],
})
}
>
<option value="">Por corresponder</option>
<option value="documented">
Reconhecimento documentado
</option>
<option value="not_required">
Excepção com norma e suporte
</option>
</select>
</label>
{text(
'recognitionReference',
'Suporte do reconhecimento ou norma de excepção',
)}
{choice(
'soleProject',
'Toda a colecta corrente respeita à actividade do projecto',
)}
{c.soleProject === 'yes'
? text(
'soleProjectReference',
'Suporte da correspondência integral da colecta ao projecto',
)
: c.soleProject === 'no' && (
<fieldset className="rounded border p-3">
<legend>
Parte da colecta respeitante ao projecto
</legend>
<p>
Indique a colecta residual do projecto
depois dos créditos
económicos/internacionais que lhe
respeitam. Documente a atribuição entre
actividades; a quota não pode exceder a
colecta global remanescente. O portal não
reparte rendimentos, custos ou perdas por
presunção.
</p>
<label className="block">
Quota residual do projecto — MZN ou n/d
<input
aria-label="CBF15 — Quota residual do projecto"
value={c.projectQuota?.amount ?? ''}
onChange={(e) =>
patch({
projectQuota: {
amount: e.target.value,
reference:
c.projectQuota
?.reference ?? '',
},
})
}
/>
</label>
<label className="block">
Suporte da quota e atribuição dos créditos
anteriores
<input
aria-label="CBF15 — Suporte da quota do projecto"
value={
c.projectQuota?.reference ?? ''
}
onChange={(e) =>
patch({
projectQuota: {
amount:
c.projectQuota
?.amount ?? '',
reference: e.target.value,
},
})
}
/>
</label>
</fieldset>
)}
<label className="block">
Local efectivo do investimento
<select
aria-label="CBF15 — Local do investimento"
value={c.location}
onChange={(e) =>
patch({
location: e.target
.value as InvestmentCredit['location'],
})
}
>
<option value="">Por documentar</option>
<option value="maputo_city">
Cidade de Maputo —5%
</option>
<option value="other">
Restantes províncias —10%, incluindo Província
de Maputo
</option>
</select>
</label>
{text(
'locationReference',
'Suporte da localização do investimento',
)}
<label className="block">
Início do prazo do crédito
<select
aria-label="CBF15 — Início"
value={c.startKind}
onChange={(e) =>
patch({
startKind: e.target
.value as InvestmentCredit['startKind'],
})
}
>
<option value="">Por documentar</option>
<option value="new">
Projecto novo — início de exploração
</option>
<option value="existing">
Projecto em funcionamento — início dos
procedimentos após aprovação
</option>
</select>
</label>
{text('startDate', 'Data do início aplicável — AAAA-MM-DD')}
{text(
'startReference',
'Suporte do início e contagem dos cinco exercícios',
)}
<p>
Indique investimento efectivamente realizado,
documentado em MZN, adquirido novo e afecto à exploração
nacional. Art15(6) exclui edifícios, viaturas ligeiras,
mobiliário/conforto/decoração, equipamentos sociais,
tecnologia de ponta e outros bens não directa e
imprescindivelmente ligados à actividade produtiva. Não
copiar investimento projectado como realização.
</p>
<button
type="button"
onClick={() =>
patch({
assets: [
...c.assets,
newInvestmentAsset(crypto.randomUUID()),
],
})
}
>
Acrescentar activo/documento
</button>
</>
)}
{!c.carryForward ? (
<button
type="button"
onClick={() => save(prepareInvestmentCarry(draft, book))}
>
Preparar transporte de crédito de anos anteriores
</button>
) : (
<fieldset className="space-y-2 rounded border p-3">
<legend>Transporte CBF15 do mesmo projecto</legend>
<p>
Declare activos de origem anterior, taxa e qualificação
históricas. Não copie saldos de simulações como uso
fiscal. O prazo original de cinco exercícios mantém-se;
investimentos correntes ficam separados.
</p>
<label className="block">
Correspondência histórica do crédito transportado
<input
aria-label="CBF15 transporte — Correspondência"
value={c.carryForward.reference}
onChange={(e) =>
patch({
carryForward: {
...c.carryForward!,
reference: e.target.value,
},
})
}
/>
</label>
<label className="block">
Investimento efectivo de 2026
<select
aria-label="CBF15 transporte — Investimento corrente"
value={c.carryForward.currentState}
onChange={(e) =>
patch({
carryForward: {
...c.carryForward!,
currentState: e.target.value as
| 'known'
| 'absent'
| '',
},
})
}
>
<option value="">Por documentar</option>
<option value="known">
Conhecido — activos correntes discriminados
</option>
<option value="absent">
Ausência documentada — apenas origens anteriores
</option>
</select>
</label>
<label className="block">
Suporte do investimento corrente/ausência
<input
aria-label="CBF15 transporte — Suporte corrente"
value={c.carryForward.currentReference}
onChange={(e) =>
patch({
carryForward: {
...c.carryForward!,
currentReference: e.target.value,
},
})
}
/>
</label>
<label className="block">
Uso efectivo de todo o crédito reconciliado
<select
aria-label="CBF15 transporte — Reconciliação do uso"
value={c.carryForward.useReconciled}
onChange={(e) =>
patch({
carryForward: {
...c.carryForward!,
useReconciled: e.target.value as
| ''
| 'yes'
| 'no',
},
})
}
>
<option value="">Por documentar</option>
<option value="yes">
Sim — inclui as origens anteriores e o crédito
corrente
</option>
<option value="no">Não reconciliado</option>
</select>
</label>
<button
type="button"
onClick={() =>
patch({
assets: [
...c.assets,
{
...newInvestmentAsset(
crypto.randomUUID(),
),
originYear: '',
originNuit: '',
originProject: '',
originReference: '',
originRate: '',
},
],
})
}
>
Acrescentar activo de origem anterior
</button>
</fieldset>
)}
{c.assets.map((r, index) => {
const change = (v: Partial<InvestmentAsset>) =>
patch({
assets: c.assets.map((x) =>
x.id === r.id ? { ...r, ...v } : x,
),
});
return (
<fieldset
key={r.id}
className="space-y-2 rounded border p-3"
>
<legend>Investimento {index + 1}</legend>
{r.originYear !== undefined && (
<>
<p>
Origem anterior — mantém a data real de
aquisição e o prazo original do projecto.
</p>
{(
[
[
'originYear',
'Exercício que originou o crédito',
],
[
'originNuit',
'NUIT do contribuinte de origem',
],
[
'originProject',
'Referência do projecto de origem',
],
[
'originRate',
'Taxa original documentada — 5 ou 10',
],
[
'originReference',
'Suporte da qualificação e crédito de origem,mesmo projecto/local/regime',
],
] as const
).map(([key, label]) => (
<label key={key} className="block">
{label}
<input
aria-label={
'CBF15 origem ' +
(index + 1) +
' — ' +
label
}
value={r[key] ?? ''}
onChange={(e) =>
change({
[key]: e.target.value,
})
}
/>
</label>
))}
</>
)}
{(
[
['issuer', 'Fornecedor/emitente'],
['document', 'Factura/documento'],
[
'date',
'Data do investimento efectivo — AAAA-MM-DD',
],
[
'proofAmount',
'Montante total do documento — MZN',
],
[
'amount',
'Investimento atribuído a este activo/projecto — MZN',
],
['currency', 'Moeda documental (recorte MZN)'],
[
'reference',
'Suporte do activo, valor e realização',
],
] as const
).map(([key, label]) => (
<label key={key} className="block">
{label}
<input
aria-label={
'CBF15 activo ' +
(index + 1) +
' — ' +
label
}
value={r[key]}
onChange={(e) =>
change({ [key]: e.target.value })
}
/>
</label>
))}
<FiscalProofParcelFields
label={'InvestimentoCBF15 ' + (index + 1)}
value={r}
change={change}
/>
{(
[
[
'realised',
'Investimento efectivamente realizado',
],
[
'newTangible',
'Activo imobilizado corpóreo adquirido em estado novo',
],
[
'domesticUse',
'Afecto à exploração do projecto em território nacional',
],
[
'directProductive',
'Directa e imprescindivelmente ligado à actividade produtiva',
],
[
'excluded15_6',
'Abrangido por alguma exclusão15(6) (para deduzir exige Não)',
],
] as const
).map(([key, label]) => (
<label key={key} className="block">
{label}
<select
aria-label={
'CBF15 activo ' +
(index + 1) +
' — ' +
label
}
value={r[key]}
onChange={(e) =>
change({ [key]: e.target.value })
}
>
<option value="">
Informação em falta
</option>
<option value="yes">
Sim — documentado
</option>
<option value="no">
Não — documentado
</option>
</select>
</label>
))}
<button
type="button"
onClick={() =>
patch({
assets: c.assets.filter(
(x) => x.id !== r.id,
),
})
}
>
Retirar activo {index + 1}
</button>
</fieldset>
);
})}
<fieldset className="space-y-2 rounded border p-2">
<legend>Uso anterior efectivo do crédito</legend>
<select
aria-label="CBF15 — Uso anterior"
value={c.previousUse.state}
onChange={(e) =>
patch({
previousUse: {
...c.previousUse,
state: e.target
.value as InvestmentCredit['previousUse']['state'],
},
})
}
>
<option value="">Informação em falta</option>
<option value="known">Montante conhecido</option>
<option value="absent">Ausência documentada</option>
</select>
<label className="block">
Montante anteriormente utilizado em todo o crédito
discriminado
<input
aria-label="CBF15 — Montante de uso anterior"
value={c.previousUse.amount}
onChange={(e) =>
patch({
previousUse: {
...c.previousUse,
amount: e.target.value,
},
})
}
/>
</label>
<label className="block">
Suporte do uso/ausência
<input
aria-label="CBF15 — Suporte do uso anterior"
value={c.previousUse.reference}
onChange={(e) =>
patch({
previousUse: {
...c.previousUse,
reference: e.target.value,
},
})
}
/>
</label>
</fieldset>
<p>
Calcule e guarde pelo percurso L8 existente. O crédito não
utilizado fica discriminado; transporte para outros exercícios e
formalidades oficiais conservam percurso próprio.
</p>
</fieldset>
);
}
@@ -4,6 +4,7 @@ import { SimulationIrpcPeriodFx } from '@/components/simulation-irpc-period-fx';
import { SimulationIrpcDepreciation } from '@/components/simulation-irpc-depreciation';
import { SimulationIrpcCosts } from '@/components/simulation-irpc-costs';
import { SimulationIrpcSchedules } from '@/components/simulation-irpc-schedules';
import { SimulationIrpcEconomicInclusion } from '@/components/simulation-irpc-economic-inclusion';
import { SimulationIrpcAssessment } from '@/components/simulation-irpc-assessment';
import { useState } from 'react';
import { SimulationIrpcTaxableBase } from '@/components/simulation-irpc-taxable-base';
@@ -438,6 +439,16 @@ export function SimulationIrpcNormal({
)}
<SimulationIrpcPeriodFx draft={draft} book={b} save={save} />
<SimulationIrpcSchedules book={b} save={save} />
<SimulationIrpcEconomicInclusion
key={JSON.stringify([
draft.context_opportunity,
b.base?.subjectNuit,
b.year,
])}
draft={draft}
book={b}
save={save}
/>
<SimulationIrpcDepreciation
draft={draft}
book={b}
@@ -0,0 +1,85 @@
import {
newPecCorrection,
type PecCorrection,
} from '@/lib/irpc-pec-correction';
export function SimulationIrpcPecCorrection({
value,
original,
subjectNuit,
onChange,
}: {
value?: PecCorrection;
original: string;
subjectNuit: string;
onChange: (c: PecCorrection) => void;
}) {
if (!value)
return (
<button
type="button"
onClick={() =>
onChange(newPecCorrection(original, subjectNuit))
}
>
Rectificar volume na simulação PEC
</button>
);
const patch = (c: Partial<PecCorrection>) => onChange({ ...value, ...c }),
field = (
key: Exclude<keyof PecCorrection, 'version' | 'apply'>,
label: string,
) => (
<label className="block">
{label}
<input
aria-label={label}
value={value[key]}
onChange={(e) => patch({ [key]: e.target.value })}
/>
</label>
);
return (
<section
aria-label="Rectificação da estimativa PEC"
className="rounded border p-3"
>
<h6>Volume de negócios corrigido</h6>
<p>
O volume original permanece {original || 'por indicar'} MZN. A
nova fonte fundamenta a correcção da simulação2026; não submete
declaração fiscal.
</p>
<label className="block">
Aplicar rectificação PEC
<select
aria-label="Aplicar rectificação PEC"
value={value.apply}
onChange={(e) =>
patch({
apply: e.target.value as PecCorrection['apply'],
})
}
>
<option value="">Por decidir</option>
<option value="yes">
Usar volume corrigido na estimativa
</option>
<option value="no">Conservar volume original</option>
</select>
</label>
<details open={value.apply !== 'no'}>
<summary>Alterar rectificação PEC</summary>
{field('originalTurnover', 'Volume original analisado — PEC')}
{field('revisedTurnover', 'Volume corrigido — PEC')}
{field('subjectNuit', 'NUIT da rectificação PEC')}
{field('turnoverYear', 'Ano do volume corrigido — PEC')}
{field('reference', 'Fonte e fundamento da rectificação PEC')}
</details>
<p>
Trocar a opção conserva os dois valores. Uma alteração da base
original exige nova conferência; recalcule o fecho e a hipótese
de tesouraria para reflectir a estimativa corrigida.
</p>
</section>
);
}
@@ -0,0 +1,151 @@
import {SimulationIrpcPecCorrection} from './simulation-irpc-pec-correction';
import {SimulationIrpcPecPlan} from './simulation-irpc-pec-plan';
import {
newPecObligation,
calculatePecObligation,
type PecObligation,
} from '@/lib/irpc-pec-obligation';
import { fractionPresentation } from '@/lib/fiscal-rational';
import type { AnnualBook } from '@/lib/irpc-annual-settlement';
export function SimulationIrpcPecObligation({
book,
onChange,
}: {
book: AnnualBook;
onChange: (p: PecObligation) => void;
}) {
const p = book.pecObligation;
if (!p)
return (
<section className="rounded border p-3">
<h5>Estimativa PEC</h5>
<p>
Preparar o total e os meses previstos conserva os
comprovativos pagos em separado.
</p>
<button
type="button"
onClick={() =>
onChange(newPecObligation(book.subjectNuit, book.year))
}
>
Simular PEC
</button>
</section>
);
const m = calculatePecObligation(
p,
book.subjectNuit,
book.year,
book.assessment,
),
patch = (changes: Partial<PecObligation>) =>
onChange({ ...p, ...changes });
const field = (
key: Exclude<keyof PecObligation, 'version' | 'scope' | 'plan' | 'correction'>,
label: string,
) => (
<label className="block">
{label}
<input
aria-label={label}
value={p[key]}
onChange={(e) => patch({ [key]: e.target.value })}
/>
</label>
);
return (
<section
aria-label="Preparação da estimativa PEC"
className="rounded border p-3"
>
<h5>Estimativa PEC — preparação</h5>
<label className="block">
Enquadramento PEC
<select
aria-label="Enquadramento PEC"
value={p.scope}
onChange={(e) =>
patch({
scope: e.target.value as PecObligation['scope'],
})
}
>
<option value="">Por confirmar</option>
<option value="ordinary_resident">
Residente geral — comercial,industrial ou agrícola,ano
civil
</option>
<option value="other">
Outro enquadramento — separado
</option>
</select>
</label>
{p.scope === 'ordinary_resident' && (
<>
{field('subjectNuit', 'NUIT — estimativa PEC')}
{field('year', 'Exercício — estimativa PEC')}
{field(
'activityStartYear',
'Ano de início efectivo da actividade — PEC',
)}
{field(
'activityReference',
'Fonte do início de actividade — PEC',
)}
{p.activityStartYear !== book.year && (
<>
{field('previousYear', 'Exercício anterior — PEC')}
{field(
'previousTurnover',
'Volume de negócios anterior — MZN',
)}
{field(
'turnoverReference',
'Fonte do volume de negócios — PEC',
)}
<SimulationIrpcPecCorrection value={p.correction} original={p.previousTurnover} subjectNuit={p.subjectNuit} onChange={correction=>patch({correction})}/>
{field(
'previousPpcPaid',
'PPC efectivamente pagos no ano anterior — MZN',
)}
{field(
'ppcReference',
'Fonte dos PPC anteriores — PEC',
)}
</>
)}
</>
)}
{m.pending.length ? (
<p role="status">{m.pending.join(' ')}</p>
) : m.notApplicable ? (
<p>Não aplicável no ano de início da actividade29(4).</p>
) : (
<>
<p>
Total estimado:{' '}
{fractionPresentation(m.total!).decimal} MZN.
</p>
<ul>
{m.months.map((month) => (
<li key={month}>{month}</li>
))}
</ul>
</>
)}
<SimulationIrpcPecPlan plan={p.plan} total={m.total} notApplicable={m.notApplicable} year={book.year} onChange={plan=>patch({plan})}/>
<p>
Guardar o rascunho conserva esta preparação. Calcular o fecho
anual inclui-a no relatório. Não regista pagamentos,cria
deduções ou executa guias. Os comprovativos efectivos permanecem
no detalhe PEC existente.
</p>
<details>
<summary>Fonte e limites PEC</summary>
<p>{m.source}</p>
</details>
</section>
);
}
@@ -0,0 +1,135 @@
import {
newPecPlan,
calculatePecPlan,
pecPlanSource,
type PecPlan,
} from '@/lib/irpc-pec-plan';
import {
fractionPresentation,
type FiscalFraction,
} from '@/lib/fiscal-rational';
export function SimulationIrpcPecPlan({
plan,
total,
notApplicable,
year,
onChange,
}: {
plan?: PecPlan;
total: FiscalFraction | null;
notApplicable: boolean;
year: string;
onChange: (p: PecPlan) => void;
}) {
if (!plan)
return (
<div className="rounded border p-3">
<p>
Prepare os montantes por prestação ou a opção de pagamento
único,sem registar pagamento.
</p>
<button
type="button"
disabled={
!total || notApplicable || BigInt(total.numerator) <= 0n
}
onClick={() => onChange(newPecPlan())}
>
Simular tesouraria PEC
</button>
</div>
);
const m = calculatePecPlan(plan, total, notApplicable, year),
patch = (p: Partial<PecPlan>) => onChange({ ...plan, ...p });
const field = (
key: 'reference' | 'june' | 'august' | 'october',
label: string,
) => (
<label className="block">
{label}
<input
aria-label={label}
value={plan[key]}
onChange={(e) => patch({ [key]: e.target.value })}
/>
</label>
);
return (
<section
aria-label="Projecção de tesouraria PEC"
className="rounded border p-3"
>
<h6>Projecção PEC</h6>
{m.pending.length ? (
<p role="status">{m.pending.join(' ')}</p>
) : (
<p>
Soma reconciliada:{' '}
{m.rows
.map(
(r) =>
r.month +
': ' +
fractionPresentation(r.amount).decimal +
' MZN',
)
.join('; ')}
. Prazo legal não validado.
</p>
)}
<details open={m.pending.length > 0}>
<summary>Alterar projecção PEC</summary>
<label className="block">
Opção de preparação PEC
<select
aria-label="Opção de preparação PEC"
value={plan.mode}
onChange={(e) =>
patch({ mode: e.target.value as PecPlan['mode'] })
}
>
<option value="">Por escolher</option>
<option value="three">
Distribuir a estimativa por três meses
</option>
<option value="single">
Concentrar a estimativa num mês
</option>
</select>
</label>
{field('reference', 'Fundamento do projecção PEC')}
{plan.mode === 'three' && (
<>
{field('june', 'PEC Junho — MZN')}
{field('august', 'PEC Agosto — MZN')}
{field('october', 'PEC Outubro — MZN')}
</>
)}
{plan.mode === 'single' && (
<label className="block">
Mês previsto do PEC único
<input
aria-label="Mês previsto do PEC único"
type="month"
value={plan.singleMonth}
onChange={(e) =>
patch({ singleMonth: e.target.value })
}
/>
</label>
)}
</details>
<p>
O mês escolhido é uma hipótese de tesouraria para avaliar a
viabilidade. A reconciliação da soma não valida os prazos
legais,emite guia ou regista pagamento. Alterar a estimativa
exige reconferir o plano. Guarde pelo rascunho e recalcule o
fecho para actualizar o relatório.
</p>
<details>
<summary>Fonte do projecção PEC</summary>
<p>{pecPlanSource}</p>
</details>
</section>
);
}
@@ -0,0 +1,169 @@
import {
newPpcObligation,
calculatePpcObligation,
ppcObligationSource,
type PpcObligation,
} from '@/lib/irpc-ppc-obligation';
import { fractionPresentation } from '@/lib/fiscal-rational';
import type { AnnualBook } from '@/lib/irpc-annual-settlement';
export function SimulationIrpcPpcObligation({
book,
onChange,
}: {
book: AnnualBook;
onChange: (value: PpcObligation) => void;
}) {
const p = book.ppcObligation;
if (!p)
return (
<section className="rounded border p-3">
<h5>Estimativa PPC</h5>
<p>
Preparar a obrigação e os meses previstos não regista
pagamentos nem cria deduções na liquidação.
</p>
<button
type="button"
onClick={() =>
onChange(newPpcObligation(book.subjectNuit, book.year))
}
>
Simular PPC
</button>
</section>
);
const m = calculatePpcObligation(
p,
book.subjectNuit,
book.year,
book.assessment,
);
const patch = (changes: Partial<PpcObligation>) =>
onChange({ ...p, ...changes });
const field = (
key:
| 'subjectNuit'
| 'year'
| 'previousYear'
| 'previousTax'
| 'previousWithholdings'
| 'assessmentReference'
| 'withholdingReference',
label: string,
) => (
<label className="block">
{label}
<input
aria-label={label}
value={p[key]}
onChange={(e) => patch({ [key]: e.target.value })}
/>
</label>
);
return (
<section
aria-label="Preparação da estimativa PPC"
className="rounded border p-3"
>
<h5>Estimativa PPC — exercício anterior</h5>
<p>
Reutiliza o titular e exercício ao iniciar. Complete a
liquidação anterior e as retenções documentadas; campos vazios
não equivalem a zero.
</p>
<label className="block">
Enquadramento PPC
<select
aria-label="Enquadramento PPC"
value={p.scope}
onChange={(e) =>
patch({
scope: e.target.value as PpcObligation['scope'],
})
}
>
<option value="">Por confirmar</option>
<option value="ordinary_resident">
Residente geral — actividade comercial, industrial ou
agrícola, ano civil
</option>
<option value="other">
Outro enquadramento — cálculo separado
</option>
</select>
</label>
{p.scope === 'ordinary_resident' && (
<>
{field('subjectNuit', 'NUIT — estimativa PPC')}
{field('year', 'Exercício — estimativa PPC')}
{field('previousYear', 'Exercício anterior — PPC')}
<label className="block">
Exercício anterior civil completo
<select
aria-label="Exercício anterior civil completo"
value={p.previousFullCalendarYear}
onChange={(e) =>
patch({
previousFullCalendarYear: e.target
.value as PpcObligation['previousFullCalendarYear'],
})
}
>
<option value="">Por confirmar</option>
<option value="yes">Sim</option>
<option value="no">
Não — anualização/período especial separado
</option>
</select>
</label>
{field(
'previousTax',
'Imposto liquidado anterior21(1) — MZN',
)}
{field(
'assessmentReference',
'Fonte da liquidação anterior — PPC',
)}
{field(
'previousWithholdings',
'Retenções na fonte anteriores — MZN',
)}
{field(
'withholdingReference',
'Fonte das retenções anteriores — PPC',
)}
</>
)}
{m.pending.length ? (
<p role="status">{m.pending.join(' ')}</p>
) : (
<>
<p>
80% antes de arredondar:{' '}
{fractionPresentation(m.annualBeforeRounding!).decimal}{' '}
MZN. Total das três prestações arredondadas:{' '}
{fractionPresentation(m.total!).decimal} MZN.
</p>
<ul>
{m.schedule.map((s) => (
<li key={s.month}>
{s.month}:{' '}
{fractionPresentation(s.amount).decimal} MZN
</li>
))}
</ul>
</>
)}
<p>
Guardar o rascunho conserva esta preparação; calcular o fecho
anual inclui-a no relatório. Só comprovativos efectivos podem
alimentar as deduções PPC abaixo. Não executa pagamento,
dispensa, guia ou juros.
</p>
<details>
<summary>Fonte e limites</summary>
<p>{ppcObligationSource}</p>
</details>
</section>
);
}
@@ -0,0 +1,277 @@
import {
newPtMzRoyaltyPreparation,
type InternationalPosition,
type PtMzRoyaltyPreparation,
} from '@/lib/irpc-international-credit';
export function SimulationIrpcPtCdt({
position: p,
patch,
prefix,
}: {
position: InternationalPosition;
patch: (v: Partial<InternationalPosition>) => void;
prefix: string;
}) {
if (!p.treatyMethod || p.treatyMethod === 'documented') return null;
const change = (key: keyof PtMzRoyaltyPreparation, value: string) =>
patch({
treatyPreparation: {
...newPtMzRoyaltyPreparation(),
...p.treatyPreparation,
[key]: value,
},
});
const facts: [keyof PtMzRoyaltyPreparation, string][] = [
['residentMz', 'Beneficiário residente convencional em Moçambique'],
['beneficialOwner', 'Beneficiário efectivo do rendimento'],
[
'payerResidentPt',
'Devedor/sociedade distribuidora residente em Portugal',
],
[
'sourcePortugal',
'Fonte portuguesa, incluindo correspondência de quem suporta o encargo',
],
['noLinkedEstablishment', 'Sem EP/instalação fixa ligada em Portugal'],
];
if (p.treatyMethod !== 'ptMzDividend')
facts.push(['armLength', 'Sem excesso por relações especiais']);
if (p.treatyMethod === 'ptMzRoyalty')
facts.push(
[
'royaltyDefinition',
'Definição convencional de royalties documentada',
],
['nonDigital', 'Caso ordinário não digital'],
);
else
facts.push([
'capitalDefinition',
'Definição convencional de dividendos/juros documentada',
]);
if (p.treatyMethod === 'ptMzInterest')
facts.push([
'governmentInterestExemption',
'Juros abrangidos pela isenção governamental do art.11(3)',
]);
return (
<fieldset className="space-y-2 rounded border p-3">
<legend>Portugal — correspondência CDT</legend>
<p>
Confirme rendimento bruto em MZN, residência convencional,
natureza e período coberto pelos documentos. O limite
convencional do caso qualificado restringe o imposto estrangeiro
elegível; não altera a parcela líquida nacional nem prova
pagamento. O limite manual anterior fica conservado.
</p>
{(
[
['gross', 'Rendimento bruto — MZN'],
['from', 'Início do período documentado — AAAA-MM-DD'],
['to', 'Fim do período documentado — AAAA-MM-DD'],
[
'reference',
'Suporte da residência, contrato e condições convencionais',
],
] as const
).map(([key, label]) => (
<label key={key} className="block">
{label}
<input
aria-label={prefix + label}
value={p.treatyPreparation?.[key] ?? ''}
onChange={(e) => change(key, e.target.value)}
/>
</label>
))}
{p.treatyMethod === 'ptMzInterest' &&
p.treatyPreparation?.governmentInterestExemption === 'yes' && (
<fieldset className="space-y-2 rounded border p-3">
<legend>
Fundamento da isenção dos juros — art.11(3)
</legend>
<label className="block">
Fundamento
<select
aria-label={
prefix +
'Fundamento da isenção governamental'
}
value={
p.treatyPreparation.governmentBasis ??
(p.treatyPreparation.governmentPayer
? 'payer'
: '')
}
onChange={(e) =>
change('governmentBasis', e.target.value)
}
>
<option value="">Escolha o fundamento</option>
<option value="payer">
Devedor público português —11(3)a
</option>
<option value="agreement">
Instituição/organismo e financiamento sob
acordo PT–MZ —11(3)b
</option>
</select>
</label>
{p.treatyPreparation.governmentBasis === 'agreement' ? (
<>
<p>
O beneficiário é o sujeito corrente:{' '}
{p.subjectNuit}. Confirme a
instituição/organismo, o financiamento por
ela concedido e o acordo entre os governos
de Portugal e Moçambique. A isenção não é
inferida pelo nome; outros
beneficiários/regimes conservam
correspondência própria.
</p>
{(
[
[
'institutionRecipient',
'Beneficiário corrente qualificado como instituição/organismo11(3)b',
],
[
'financingGranted',
'Juros resultam de financiamento concedido por esse beneficiário',
],
[
'bilateralAgreement',
'Financiamento no âmbito de acordo concluído entre os governos PT–MZ',
],
] as const
).map(([key, label]) => (
<label key={key} className="block">
{label}
<select
aria-label={prefix + label}
value={
p.treatyPreparation?.[key] ?? ''
}
onChange={(e) =>
change(key, e.target.value)
}
>
<option value="">
Informação em falta
</option>
<option value="yes">
Sim — documentado
</option>
<option value="no">
Não — documentado
</option>
</select>
</label>
))}
{(
[
[
'institutionReference',
'Suporte da qualificação do beneficiário corrente',
],
[
'financingReference',
'Suporte do financiamento e ligação aos juros',
],
[
'agreementReference',
'Acordo PT–MZ e cláusula aplicável a este financiamento',
],
] as const
).map(([key, label]) => (
<label key={key} className="block">
{label}
<input
aria-label={prefix + label}
value={
p.treatyPreparation?.[key] ?? ''
}
onChange={(e) =>
change(key, e.target.value)
}
/>
</label>
))}
</>
) : (
<>
<p>
Qualifique o devedor público português com
suporte próprio. A isenção na fonte não
gera, no portal, reembolso do imposto pago.
</p>
<label className="block">
Devedor dos juros
<select
aria-label={
prefix + 'Devedor público português'
}
value={
p.treatyPreparation
.governmentPayer ?? ''
}
onChange={(e) =>
change(
'governmentPayer',
e.target.value,
)
}
>
<option value="">Por documentar</option>
<option value="state">
Estado português
</option>
<option value="subdivision">
Subdivisão política/administrativa
portuguesa
</option>
<option value="local">
Autarquia portuguesa
</option>
</select>
</label>
<label className="block">
Suporte da qualificação do devedor
<input
aria-label={
prefix +
'Suporte do devedor público'
}
value={
p.treatyPreparation
.governmentPayerReference ?? ''
}
onChange={(e) =>
change(
'governmentPayerReference',
e.target.value,
)
}
/>
</label>
</>
)}
</fieldset>
)}
{facts.map(([key, label]) => (
<label key={key} className="block">
{label}
<select
aria-label={prefix + label}
value={p.treatyPreparation?.[key] ?? ''}
onChange={(e) => change(key, e.target.value)}
>
<option value="">Informação em falta</option>
<option value="yes">Sim — documentado</option>
<option value="no">Não — documentado</option>
</select>
</label>
))}
</fieldset>
);
}
@@ -28,7 +28,7 @@ const states = [
['absent', 'Ausência declarada com suporte'],
['not_applicable', 'Tratamento não aplicável — justificar'],
] as const;
function QualificationFields({
export function QualificationFields({
label,
value,
onChange,
+161 -1
View File
@@ -241,6 +241,162 @@ const translations: Record<string, Record<string, string>> = {
},
};
const sharedTranslations: Record<string, [string, string, string]> = {
'Criar conta': ['Create account', 'Créer un compte', 'Crear cuenta'],
'Entrar na sua conta': [
'Sign in to your account',
'Connectez-vous à votre compte',
'Inicie sesión en su cuenta',
],
'Área reservada': ['Private area', 'Espace réservé', 'Área reservada'],
'Bolsa de Parcerias, Investidores e Capital': [
'Partnership, Investor and Capital Exchange',
'Bourse des partenariats, investisseurs et capitaux',
'Bolsa de Alianzas, Inversores y Capital',
],
'Aceda à sua conta Tempo.': [
'Access your Tempo account.',
'Accédez à votre compte Tempo.',
'Acceda a su cuenta Tempo.',
],
'Crie uma conta simples para continuar e acompanhar a sua operação. Não é necessário configurar MFA para participar.':
[
'Create an account to continue and follow your operation. You do not need to set up MFA to participate.',
'Créez un compte pour poursuivre et suivre votre opération. La configuration de l’authentification multifacteur n’est pas nécessaire pour participer.',
'Cree una cuenta para continuar y seguir su operación. No necesita configurar la autenticación multifactor para participar.',
],
'Entre para continuar a operação que escolheu. Os dados preenchidos permanecem guardados neste navegador.':
[
'Sign in to continue the operation you chose. Your entered data remains saved in this browser.',
'Connectez-vous pour poursuivre l’opération choisie. Les données saisies restent enregistrées dans ce navigateur.',
'Inicie sesión para continuar la operación elegida. Los datos introducidos permanecen guardados en este navegador.',
],
'Palavra-passe': ['Password', 'Mot de passe', 'Contraseña'],
'Mostrar palavra-passe': [
'Show password',
'Afficher le mot de passe',
'Mostrar contraseña',
],
'Ocultar palavra-passe': [
'Hide password',
'Masquer le mot de passe',
'Ocultar contraseña',
],
'Confirmar palavra-passe': [
'Confirm password',
'Confirmer le mot de passe',
'Confirmar contraseña',
],
Nome: ['Name', 'Nom', 'Nombre'],
'Nome completo': ['Full name', 'Nom complet', 'Nombre completo'],
'Esqueceu a palavra-passe?': [
'Forgot your password?',
'Mot de passe oublié ?',
'¿Olvidó su contraseña?',
],
'Manter sessão iniciada': [
'Keep me signed in',
'Rester connecté',
'Mantener la sesión iniciada',
],
'Ainda não tem conta?': [
'Don’t have an account?',
'Vous n’avez pas de compte ?',
'¿Aún no tiene cuenta?',
],
'Já tem conta?': [
'Already have an account?',
'Vous avez déjà un compte ?',
'¿Ya tiene cuenta?',
],
'Entrar com chave de acesso': [
'Sign in with a passkey',
'Se connecter avec une clé d’accès',
'Iniciar sesión con clave de acceso',
],
'A autenticar…': [
'Signing in…',
'Connexion en cours…',
'Iniciando sesión…',
],
'Ou continuar com email': [
'Or continue with email',
'Ou continuer avec l’e-mail',
'O continuar con correo electrónico',
],
'O email ou a palavra-passe não correspondem aos nossos registos.': [
'The email or password does not match our records.',
'L’adresse e-mail ou le mot de passe ne correspond pas à nos informations.',
'El correo electrónico o la contraseña no coinciden con nuestros registros.',
],
'A palavra-passe está incorrecta.': [
'The password is incorrect.',
'Le mot de passe est incorrect.',
'La contraseña es incorrecta.',
],
'Atalho para o conteúdo': [
'Skip link',
'Lien d’accès rapide',
'Enlace de acceso rápido',
],
'Saltar para o conteúdo': [
'Skip to content',
'Aller au contenu',
'Ir al contenido',
],
'Caminho de navegação': [
'Breadcrumb',
'Fil d’Ariane',
'Ruta de navegación',
],
'Contacto de apoio ao investidor': [
'Investor support contact',
'Contact de l’assistance aux investisseurs',
'Contacto de apoyo al inversor',
],
'Contactar Apoio ao Investidor': [
'Contact investor support',
'Contacter l’assistance aux investisseurs',
'Contactar con apoyo al inversor',
],
'Ambiente de homologação': [
'Review environment',
'Environnement de validation',
'Entorno de validación',
],
'Homologação reservada à revisão da Tempo. Consulte o conteúdo público; entre na sua conta quando iniciar uma operação.':
[
'This review environment is reserved for Tempo. Browse public content; sign in when starting an operation.',
'Cet environnement de validation est réservé à Tempo. Consultez le contenu public ; connectez-vous pour commencer une opération.',
'Este entorno de validación está reservado para Tempo. Consulte el contenido público; inicie sesión al comenzar una operación.',
],
'Idioma da navegação': [
'Navigation language',
'Langue de navigation',
'Idioma de navegación',
],
'Navegação institucional': ['Language', 'Langue', 'Idioma'],
Valores: ['Values', 'Valeurs', 'Valores'],
'Ver menos': ['Show less', 'Voir moins', 'Ver menos'],
'Ver mais': ['Show more', 'Voir plus', 'Ver más'],
Voltar: ['Back', 'Retour', 'Volver'],
'Anos de Experiência': [
'Years of experience',
'Années d’expérience',
'Años de experiencia',
],
'Projetos Realizados': [
'Projects completed',
'Projets réalisés',
'Proyectos realizados',
],
};
for (const [source, values] of Object.entries(sharedTranslations)) {
(['en', 'fr', 'es'] as const).forEach((locale, index) => {
translations[locale][source] = values[index];
});
}
const TempoUi = createContext({
language: 'pt',
setLanguage: (_language: string) => {},
@@ -255,13 +411,17 @@ export function TempoUiProvider({ children }: { children: ReactNode }) {
useEffect(() => {
try {
const saved = localStorage.getItem('tempo-navigation-language');
if (saved && ['pt', 'en', 'fr', 'es'].includes(saved))
if (saved && /^[a-zA-Z][a-zA-Z0-9-]{0,19}$/.test(saved))
setCurrentLanguage(saved);
} catch {
/* Navigation remains usable without browser storage. */
}
}, []);
useEffect(() => {
document.documentElement.lang = language;
}, [language]);
const setLanguage = (next: string) => {
if (!/^[a-zA-Z][a-zA-Z0-9-]{0,19}$/.test(next)) return;
setCurrentLanguage(next);
try {
localStorage.setItem('tempo-navigation-language', next);
+5 -1
View File
@@ -10,8 +10,9 @@ const navigation = [
export function TempoFooter() {
const { t, language } = useTempoUi();
const { portalInstitutionalContactEmail } = usePage<{
const { portalInstitutionalContactEmail, portalOperator } = usePage<{
portalInstitutionalContactEmail?: string | null;
portalOperator?: string | null;
}>().props;
return (
<footer
@@ -27,6 +28,9 @@ export function TempoFooter() {
>
<PortalProductIdentity />
</Link>
<p className="text-sm text-slate-300">
{t('Operado por')} {portalOperator || 'Tempo, Lda.'}
</p>
<div className="flex items-start gap-4">
<MapPin
className="mt-1 size-[22px] shrink-0 text-[#ed1c24]"
+24 -9
View File
@@ -8,6 +8,9 @@ import {
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { useTempoUi } from '@/components/tempo-brand';
import { getLanguages } from '@/translation/service';
import { withPortugueseFirst } from '@/translation/utils';
import type { TranslationLanguage } from '@/translation/types';
type Weather = { temp: number; description: string; humidity?: number };
@@ -97,12 +100,28 @@ export function TempoWeather() {
}
export function TempoLanguageSelector() {
const { language, setLanguage } = useTempoUi();
const { language, setLanguage, t } = useTempoUi();
const [languages, setLanguages] = useState<TranslationLanguage[]>([
{ code: 'pt', name: 'Português' },
{ code: 'en', name: 'English' },
{ code: 'fr', name: 'Français' },
{ code: 'es', name: 'Español' },
]);
const [available, setAvailable] = useState(true);
useEffect(() => {
const controller = new AbortController();
getLanguages(controller.signal).then((items) => {
const portuguese = items.find((item) => item.code === 'pt');
if (!portuguese) throw new Error('Portuguese model unavailable');
setLanguages(withPortugueseFirst(items.filter((item) => item.code === 'pt' || !portuguese.targets || portuguese.targets.includes(item.code))));
}).catch(() => { if (!controller.signal.aborted) setAvailable(false); });
return () => controller.abort();
}, []);
return (
<DropdownMenu modal={false}>
<DropdownMenuTrigger
className="tempo-utility-button"
aria-label="Idioma da navegação"
aria-label={t('Idioma da navegação')}
>
<Globe size={16} aria-hidden="true" />
<span className="text-xs font-semibold uppercase">
@@ -116,14 +135,10 @@ export function TempoLanguageSelector() {
className="tempo-dropdown min-w-44"
>
<DropdownMenuLabel className="text-xs text-slate-500">
Navegação institucional
{t('Navegação institucional')}
</DropdownMenuLabel>
{[
['pt', 'Português'],
['en', 'English'],
['fr', 'Français'],
['es', 'Español'],
].map(([code, name]) => (
{!available && <DropdownMenuLabel className="text-xs text-slate-500">Tradução temporariamente indisponível</DropdownMenuLabel>}
{languages.map(({ code, name }) => (
<DropdownMenuItem
key={code}
onSelect={() => setLanguage(code)}
@@ -62,21 +62,32 @@ export function TerritoryGeometryDetails({
{!context.selected_geometry_available &&
context.areas.length > 0 && (
<p role="note">
O mapa mostra apenas limites dos níveis
superiores como contexto. Estes limites não
representam o território seleccionado.
O mapa mostra outros limites da hierarquia como
contexto. Estes limites não representam o
território seleccionado.
</p>
)}
<p>
{context.context_geometry_count} limites disponíveis na
hierarquia selecionada. São contexto administrativo, não
hierarquia consultada. São contexto administrativo, não
perímetro nem disponibilidade de investimento. A edição
e a atualidade jurídica são informações distintas.
</p>
{context.child_coverage?.location_type && (
<p role="status">
Subdivisões do nível imediatamente inferior com
limite nesta resposta:{' '}
{context.child_coverage.represented_count} de{' '}
{context.child_coverage.registered_count}{' '}
referências acessíveis. As restantes não têm limite
apresentado nesta consulta; isso não significa
inexistência do território.
</p>
)}
{context.truncated && (
<p role="alert">
Exibidos os primeiros 100 limites; cobertura
incompleta.
Consulta limitada a 100 geometrias e 500
subdivisões; cobertura incompleta.
</p>
)}
{renderMap && context.areas.length > 0 && (
@@ -93,7 +104,9 @@ export function TerritoryGeometryDetails({
{area.title} ·{' '}
{area.selected_territory
? 'Território seleccionado'
: 'Contexto de nível superior'}{' '}
: area.child_territory
? 'Subdivisão do território seleccionado'
: 'Contexto de nível superior'}{' '}
· Fonte:{' '}
{area.source_reference ?? 'Não indicada'} ·
Edição: {area.source_date ?? 'Não indicada'}
@@ -0,0 +1,285 @@
import { useEffect, useState } from 'react';
import InvestmentMapIndicators, {
type QuantitativeMapContext,
} from './investment-map-indicators';
import CatalogueInvestmentMap from './catalogue-investment-map';
import type {
InvestmentMapArea,
InvestmentMapFeature,
} from './interactive-investment-map';
export type ThematicMapRecord = {
id: number;
title: string;
url: string;
location: string;
state: string;
limitations: string | null;
evidence: {
id: string;
category:
| 'resource'
| 'infrastructure'
| 'workforce'
| 'economic_potential';
source_category?: string;
statement?: string | null;
source?: string | null;
locator?: string | null;
period?: string | null;
scope?: string | null;
coverage?: string | null;
verified_on?: string | null;
}[];
};
const labels = {
resource: 'Recursos documentados',
infrastructure: 'Infra-estruturas documentadas',
workforce: 'Mão-de-obra documentada',
economic_potential: 'Potencial económico — contexto documentado',
};
const sourceLabels: Record<string, string> = {
activity: 'Actividade',
supply: 'Oferta',
demand: 'Procura',
workforce: 'Mão-de-obra',
resource: 'Recurso',
infrastructure: 'Infra-estrutura',
};
const value = (text?: string | null) => text?.trim() || 'Não indicado';
export default function ThematicInvestmentMap({
points,
areas,
records,
territoryId,
truncated,
quantitative = null,
}: {
points: InvestmentMapFeature[] | null;
areas: InvestmentMapArea[];
records: ThematicMapRecord[];
territoryId?: string;
truncated: boolean;
quantitative?: QuantitativeMapContext | null;
}) {
const [theme, setTheme] = useState('all');
useEffect(() => {
try {
const saved = window.sessionStorage.getItem(
'investmoz-map-theme-v1',
);
if (saved && Object.hasOwn(labels, saved)) setTheme(saved);
} catch {
// The server and first client render use the same default layer.
}
}, []);
const selectTheme = (next: string) => {
setTheme(next);
try {
window.sessionStorage.setItem('investmoz-map-theme-v1', next);
} catch {
// Keep the map usable when session storage is unavailable.
}
};
const selected = records
.map((record) => ({
...record,
evidence: record.evidence.filter(
(proof) => theme === 'all' || proof.category === theme,
),
}))
.filter((record) => record.evidence.length > 0);
const ids = new Set(selected.map((record) => record.id));
const visiblePoints =
theme === 'all'
? points
: (points?.filter((point) => ids.has(point.properties.id)) ?? null);
const visibleAreas =
theme === 'all'
? areas
: areas.filter(
(area) =>
area.opportunity_id !== undefined &&
ids.has(area.opportunity_id),
);
const countLabel = (count: number) =>
`${count} ${count === 1 ? 'ficha' : 'fichas'}`;
const located = new Set((points ?? []).map((point) => point.properties.id));
return (
<div className="space-y-3">
<section
className="space-y-3 rounded border p-3"
aria-label="Camadas temáticas documentadas"
>
<label
className="block font-semibold"
htmlFor="investment-map-theme"
>
Camada temática do mapa
</label>
<select
id="investment-map-theme"
className="w-full rounded border p-2"
value={theme}
disabled={points === null}
onChange={(event) => selectTheme(event.target.value)}
>
<option value="all">Todas as oportunidades</option>
{Object.entries(labels).map(([key, label]) => (
<option key={key} value={key}>
{label} (
{countLabel(
records.filter((record) =>
record.evidence.some(
(proof) => proof.category === key,
),
).length,
)}{' '}
de oportunidades)
</option>
))}
</select>
<p>
A camada filtra os locais e áreas das oportunidades com
evidência pública seleccionada. Os marcadores localizam a
oportunidade; não representam a posição exacta, capacidade
ou disponibilidade do recurso ou da infra-estrutura. A
pesquisa geral mantém os seus filtros e resultados. As
contagens desta secção são de oportunidades; os indicadores
territoriais têm contagem própria.
</p>
<p>
Mão-de-obra: a evidência pode descrever necessidades ou
qualificações; não mede trabalhadores disponíveis. Potencial
económico: contexto de actividade, oferta e procura, sem
índice, previsão de retorno ou viabilidade calculados.
Fontes e períodos não são agregados como se fossem
comparáveis.
</p>
{truncated && (
<p role="note">
A resposta está limitada; a cobertura temática refere-se
apenas às fichas recebidas.
</p>
)}
{points === null ? (
<p role="status">A carregar evidências temáticas…</p>
) : (
<>
<p role="status">
{countLabel(selected.length)} com evidência temática
pública nesta resposta;{' '}
{
selected.filter(
(record) => !located.has(record.id),
).length
}{' '}
sem ponto público utilizável. Ausência de evidência
não significa ausência de recursos ou actividade
económica.
</p>
{selected.length === 0 && (
<p>
Sem evidência pública local nas oportunidades
desta camada e estes filtros. Não foram criadas
posições no mapa.
</p>
)}
{selected.length > 0 && (
<details className="space-y-2">
<summary>
Evidências e fontes da camada (
{countLabel(selected.length)})
</summary>
{selected.map((record) => (
<details
key={record.id}
className="rounded border p-2"
>
<summary>
{record.title} —{' '}
{located.has(record.id)
? 'Com ponto da oportunidade'
: 'Sem ponto público utilizável'}
</summary>
<a
className="underline"
href={record.url}
>
Consultar ficha
</a>
<p>
Localização da ficha:{' '}
{value(record.location)}. O âmbito
de cada evidência está indicado
abaixo.
</p>
<p>
Situação publicada:{' '}
{record.state === 'confirmed'
? 'Confirmada no âmbito da síntese publicada'
: 'Hipótese'}
. Limitações:{' '}
{value(record.limitations)}
</p>
<ul>
{record.evidence.map((proof) => (
<li
key={proof.id}
className="my-2 space-y-1 border-t pt-2"
>
<p className="font-semibold">
{labels[proof.category]}
</p>
{proof.source_category && (
<p>
Categoria de origem:{' '}
{sourceLabels[
proof
.source_category
] ?? 'Não indicada'}
</p>
)}
<p>
{value(proof.statement)}
</p>
<p>
Fonte:{' '}
{value(proof.source)} ·
Localizador:{' '}
{value(proof.locator)}
</p>
<p>
Período:{' '}
{value(proof.period)} ·
Verificação registada:{' '}
{value(
proof.verified_on,
)}
</p>
<p>
Âmbito:{' '}
{value(proof.scope)} ·
Cobertura e limites:{' '}
{value(proof.coverage)}
</p>
</li>
))}
</ul>
</details>
))}
</details>
)}
</>
)}
</section>
<InvestmentMapIndicators context={quantitative} theme={theme} />
<CatalogueInvestmentMap
points={visiblePoints}
areas={visibleAreas}
territoryId={territoryId}
/>
</div>
);
}
@@ -6,13 +6,21 @@ export type TerritoryGeometryContext = {
context_geometry_count: number;
areas: InvestmentMapArea[];
truncated: boolean;
child_coverage?: {
location_type: string | null;
registered_count: number;
represented_count: number;
reference_limit: number;
geometry_limit: number;
} | null;
};
export function useTerritoryGeometry(
territoryId?: string,
endpoint = '/api/v1/investment/territory-geometries',
includeChildren = false,
) {
const key = JSON.stringify([territoryId, endpoint]);
const key = JSON.stringify([territoryId, endpoint, includeChildren]);
const [result, setResult] = useState<{
key: string;
context: TerritoryGeometryContext | null;
@@ -23,7 +31,7 @@ export function useTerritoryGeometry(
setResult({ key, context: null, error: '' });
if (!territoryId) return () => controller.abort();
void fetch(
`${endpoint}?${new URLSearchParams({ territory_id: territoryId })}`,
`${endpoint}?${new URLSearchParams({ territory_id: territoryId, ...(includeChildren ? { include_children: '1' } : {}) })}`,
{
signal: controller.signal,
headers: { Accept: 'application/json' },
@@ -52,6 +60,6 @@ export function useTerritoryGeometry(
});
});
return () => controller.abort();
}, [key, territoryId, endpoint]);
}, [key, territoryId, endpoint, includeChildren]);
return result.key === key ? result : { key, context: null, error: '' };
}
+1 -1
View File
@@ -1,4 +1,4 @@
import AppLayoutTemplate from '@/layouts/app/app-sidebar-layout';
import AppLayoutTemplate from '@/layouts/app/app-header-layout';
import type { BreadcrumbItem } from '@/types';
export default function AppLayout({

Some files were not shown because too many files have changed in this diff Show More