Compare commits

...
Author SHA1 Message Date
nala 4098192a05 Record installed continuity corrections and destination delta checks 2026-10-23 15:48:52 +02:00
nala 6b544548d3 Fix compressed draft saves provenance and Beira documentary continuity
tests / ci (push) Canceled after 0s
2026-10-23 15:41:37 +02:00
nala c3f7c3ad10 Record homologation product journey findings and publication status 2026-10-23 15:26:38 +02:00
nala 445aa7e69d Record consolidated adaptive portfolio installation receipt 2026-10-23 15:17:59 +02:00
nala 7c1f532134 Preserve inactive document history in adaptive reports
tests / ci (push) Canceled after 0s
2026-10-23 15:14:44 +02:00
nala 115c773162 Record QA continuity recovery after return reload and login
tests / ci (push) Canceled after 0s
2026-10-23 15:03:22 +02:00
nala 3d877ec745 Preserve selected portfolio context through investment preparation 2026-10-23 14:35:50 +02:00
nala 6948ed62fc Consolidate adaptive preparation QA delivery and accepted evidence 2026-10-23 14:19:05 +02:00
nala 7d798579a6 Reconcile portfolio recovery evidence and integrated journey gap 2026-10-23 14:17:42 +02:00
nala 5bda74aedc Adapt land status to saved investment land need 2026-10-23 14:13:58 +02:00
nala f2150b1912 Reuse investment context as editable simulator summaries 2026-10-23 14:03:37 +02:00
nala 7ca94967f3 Adapt preparation presentation to declared applicability 2026-10-23 13:53:02 +02:00
nala e1f010f941 fix: preserve corridor filters and keep investment previews readable
tests / ci (push) Canceled after 0s
2026-10-23 13:30:10 +02:00
nala 0025309b98 feat: adapt investment territories to Tempo corridor configuration
tests / ci (push) Canceled after 0s
2026-10-23 13:24:58 +02:00
nala a0e80db4ec docs: confirm opportunity preparation continuity in homologation
tests / ci (push) Canceled after 0s
2026-10-23 12:31:36 +02:00
nala 7226287526 fix: keep opportunity preparation focused on the pending selection
tests / ci (push) Canceled after 0s
2026-10-23 12:29:26 +02:00
nala f1ac82ae4d docs: record clean investor entry verified in homologation
tests / ci (push) Canceled after 0s
2026-10-23 12:20:28 +02:00
nala 98474777f6 fix: remove internal metadata from investor dossier presentation
tests / ci (push) Canceled after 0s
2026-10-23 12:18:25 +02:00
nala 3848f29bea docs: record corridor catalogue verification in homologation
tests / ci (push) Canceled after 0s
2026-10-23 12:12:05 +02:00
nala 203492997e feat: explore the public opportunity catalogue by corridor
tests / ci (push) Canceled after 0s
2026-10-23 12:08:33 +02:00
nala 165c2f6171 fix: keep internal agribusiness delivery notes out of portal presentation
tests / ci (push) Canceled after 0s
2026-10-23 11:22:39 +02:00
nala 4ad1bf402b docs: record authenticated publication of 34 agribusiness opportunities
tests / ci (push) Canceled after 0s
2026-10-23 11:12:45 +02:00
nala e12ef6ad42 Record installed agribusiness workflow, preserved destination and pending real-account publication
tests / ci (push) Canceled after 0s
2026-10-23 11:00:30 +02:00
nala c641b267ac Publish agribusiness lot through scoped management and extend existing dossier for multiple opportunities
tests / ci (push) Canceled after 0s
2026-10-23 10:49:50 +02:00
nala 7d08c2c664 Register 34 private agribusiness opportunities with promotional management and investor paths in QA 2026-10-23 10:15:41 +02:00
nala 761035aefb Record homologation agribusiness installation and preserved QA evidence
tests / ci (push) Canceled after 0s
2026-10-23 09:58:13 +02:00
nala 20a4099d10 Present 34 agribusiness opportunities in existing corridor sections
tests / ci (push) Canceled after 0s
2026-10-23 09:56:22 +02:00
nala e03de682ca Preserve completed features and restore approved portfolio visibility
tests / ci (push) Canceled after 0s
2026-10-23 09:19:25 +02:00
nala 37a8eb015a docs: record private legacy registration and authenticated editorial access
tests / ci (push) Canceled after 0s
2026-10-22 23:55:57 +02:00
nala 65eddf6bc7 merge: install reviewed private legacy editorial workflow
tests / ci (push) Canceled after 0s
2026-10-22 23:40:27 +02:00
97 changed files with 6609 additions and 328 deletions
+18
View File
@@ -1,5 +1,13 @@
# Instruções permanentes do projeto InvesteMoz
## Percurso adaptativo — decisão expressa da Tempo, 23/10/2026
Carteira/projectos âncora e percurso adaptativo têm diagnósticos distintos, mas fazem parte da mesma experiência e responsabilidade de correcção. Separar o diagnóstico não autoriza adiamento, desligamento do percurso ou perda de contexto entre corredor/localização, oportunidades, associações documentadas a âncoras/clusters, dossier e simulador. A reposição prioritária só se considera demonstrada com causa delimitada e disponibilidade recuperada, reutilizando o inventário e os recibos existentes; melhorias noutro ponto não a comprovam. Consultar os registos de reposição antes de voltar a declarar causa desconhecida ou registos desaparecidos.
Apresentar apenas o necessário ao investimento em preparação, com entrada por ideia, oportunidade, sector ou localização, reutilizando informação válida e autorizada da oportunidade, perfil, organização, dossier e fontes anteriores. As etapas são adaptáveis, não seis formulários obrigatórios. Não sobrescrever edições deliberadas; explicar divergências e impactos antes da confirmação. Conservar escolhas ao avançar, regressar, actualizar e autenticar-se. Seleccionar opções únicas e pedir apenas dados em falta ou desactualizados.
Ocultar campos e acções sem aplicação, conservando explicação breve e histórico; desconhecido não significa não aplicável. Na selecção múltipla, partilhar dados comuns sem perder os requisitos específicos de cada oportunidade. Consulta pública livre; autenticação no momento de manifestar interesse ou iniciar operação privada. Relatórios e exportações devem reflectir o contexto e as etapas efectivamente aplicáveis. O utilizador deve perceber onde está, o que falta e a próxima acção. Aperfeiçoar os componentes existentes por incrementos e registar cobertura na matriz existente; esta instrução não declara concluída a sua implementação transversal.
## Regras permanentes do InvesteMoz — decisão da Tempo, 12/10/2026
1. O portal InvesteMoz é propriedade da Tempo, Lda.
@@ -46,3 +54,13 @@ Referenciar estas orientações nos requisitos correspondentes da matriz existen
A legislação, os formulários e os procedimentos oficiais disponibilizados são fontes válidas para implementar os requisitos. As decisões e procedimentos da Tempo orientam a sua utilização no portal. Traduzir essas fontes em campos, regras, documentos, cálculos e percursos executáveis com rastreabilidade, sem exigir nova validação jurídica genérica ou criar aprovadores, restrições ou condições por interpretação do Codex.
Uma divergência concreta deve identificar os trechos, períodos e comportamento afectados; prosseguir com os restantes requisitos. Uma decisão de implementação em falta deve ser apresentada especificamente à Tempo, sem a substituir por regra inventada. Rever e remover bloqueios anteriores sem fundamento numa fonte aplicável ou decisão expressa da Tempo, preservando dados e histórico. A rastreabilidade explica a execução e não constitui uma autoridade adicional. Mantêm-se os limites autorizados de QA e das alterações no activo.
## Preservação do concluído — pedido expresso da Tempo, 23/10/2026
Antes de avançar com novas rondas de testes/validações, inventariar na matriz e no manual existentes as funcionalidades concluídas e as funcionalidades desaparecidas, e repor as perdas confirmadas. Preservar as aprovações, dados, originais, relações, versões, histórico e acessos já concluídos. Não transformar uma entrega anterior em funcionalidade nova nem exigir novamente a decisão organizacional já registada.
Comparar código, schema, dados, configuração e permissões entre o último estado demonstrado e o destino. Distinguir eliminação, perda de visibilidade, diferença de ambiente, pendência real e estado documental desactualizado; uma página vazia ou a falta de nova prova não apagam a aceitação anterior. Reutilizar as provas aceites e conferir apenas o delta da reposição. Cada entrega seguinte deve explicar qualquer redução do âmbito previamente disponível, em vez de a aceitar como consequência normal da implementação.
Transporte privado não equivale à revogação de uma divulgação anterior aprovada. Reconciliar a decisão anterior de forma delimitada e auditada, sem publicar automaticamente conteúdos novos, reviver exemplares arquivados ou sobrescrever edições posteriores. Mantêm-se backup/restauro, autenticação e os limites concretos de cada tarefa.
Escolha técnica de verificação: comparar dados protegidos por segurança por linha (RLS) com identidades e contexto de acesso equivalentes. Uma leitura da aplicação sem actor pode ocultar registos que o utilizador do backup consegue ler; confirmar essa diferença antes de declarar perda ou propor reposição. Esta orientação não altera permissões do portal nem autoriza contornar RLS numa operação da equipa.
@@ -0,0 +1,73 @@
<?php
namespace App\Http\Controllers;
use App\Models\Opportunity;
use App\Services\CorridorAgribusinessOpportunities;
use App\Services\OpportunityManagement;
use App\Services\OpportunityPromotion;
use App\Services\PortalProfileAccess;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Inertia\Inertia;
final class CorridorAgribusinessController extends Controller
{
private function manifest(CorridorAgribusinessOpportunities $service): string
{
return hash('sha256', json_encode($service->entries(), JSON_THROW_ON_ERROR));
}
public function show(Request $request, CorridorAgribusinessOpportunities $service)
{
$access = app(PortalProfileAccess::class);
abort_unless($access->internalAssignment($request->user(), 'investment_project') || $access->has($request->user(), 'publisher'), 403);
$records = Opportunity::withTrashed()->whereIn('management_creation_key', array_column($service->entries(), 'key'))->get()->keyBy('management_creation_key');
foreach ($records as $record) abort_unless($access->canView($request->user(), $record), 403);
return Inertia::render('management/corridor-agribusiness', ['manifestHash' => $this->manifest($service),
'canRegister' => (bool) $access->internalAssignment($request->user(), 'investment_project'),
'entries' => array_map(function ($entry) use ($records) {
$r = $records->get($entry['key']);
return [...$entry, 'id' => $r?->id, 'status' => $r?->status, 'href' => $r ? '/oportunidades/'.$r->slug : null];
}, $service->entries())]);
}
public function register(Request $request, CorridorAgribusinessOpportunities $service)
{
$data = $request->validate(['manifest_hash' => 'required|string|size:64', 'confirm' => 'required|accepted']);
abort_unless(hash_equals($this->manifest($service), $data['manifest_hash']), 409, 'A fonte mudou; recupere a prévia.');
$access = app(PortalProfileAccess::class);
$grant = $access->internalAssignment($request->user(), 'investment_project');
abort_unless($grant && $access->mfa($request->user()), 403, 'Use a designação existente e MFA recente.');
return response()->json(['records' => $service->register($request->user(), $grant->organization_id)]);
}
public function preview(Request $request, CorridorAgribusinessOpportunities $service)
{
$previews = [];
foreach ($service->entries() as $entry) {
$record = Opportunity::where('management_creation_key', $entry['key'])->firstOrFail();
$input = data_get($record->workflow_context, 'promotional_summary.input');
abort_unless(is_array($input), 409, 'Complete a selecção pública na divulgação individual.');
$previews[] = ['id' => $record->id, ...app(OpportunityPromotion::class)->preview($request->user(), $record,
[...$input, 'source_date' => $input['source_date'] ?: null, 'action' => 'disclose', 'etag' => app(OpportunityManagement::class)->etag($record)])];
}
return response()->json(['previews' => $previews]);
}
public function disclose(Request $request, CorridorAgribusinessOpportunities $service)
{
$data = $request->validate(['confirm' => 'required|accepted', 'reason' => 'required|string|min:20|max:1000',
'previews' => 'required|array|size:34', 'previews.*.id' => 'required|integer|distinct',
'previews.*.token' => 'required|string|max:300000', 'previews.*.operation_key' => 'required|uuid|distinct']);
$expected = Opportunity::whereIn('management_creation_key', array_column($service->entries(), 'key'))->pluck('id')->sort()->values()->all();
$supplied = array_column($data['previews'], 'id'); sort($supplied);
abort_unless($expected === $supplied && count($expected) === 34, 409, 'A prévia deve identificar exactamente as 34 fichas deste lote.');
return response()->json(DB::transaction(function () use ($request, $data) {
$result = [];
foreach ($data['previews'] as $preview) $result[] = app(OpportunityPromotion::class)->confirm($request->user(), $preview['id'],
[...$preview, 'confirm' => true, 'reason' => $data['reason']]);
return ['records' => $result];
}));
}
}
@@ -26,6 +26,7 @@ class InvestmentDiscoveryController extends Controller
}
return Inertia::render('investment/catalogue', ['filters' => $filters, 'catalogueUrl' => $request->routeIs('investment.education') ? '/educacao' : '/oportunidades', 'references' => $catalogue->references(false, $filters),
'corridorGroups' => ($filters['view'] ?? '') === 'corridors' ? app(\App\Services\OpportunityCorridorExplorer::class)->groups($filters) : [],
'territorySelection' => app(InvestmentGeography::class)->selection($filters),
'guides' => app(\App\Services\SectorInvestment::class)->guides(\App\Models\Sector::where('slug', $filters['sector'] ?? '')->where('is_active', true)->whereNotNull('published_at')->first()),
'results' => $catalogue->query($filters)->paginate(12)->withQueryString(),
@@ -88,7 +89,7 @@ class InvestmentDiscoveryController extends Controller
$data = $request->validate(['zone_id' => ['nullable', 'uuid'], 'from_dossier' => ['nullable', 'uuid']]);
$dossierOrigin = isset($data['from_dossier']) ? InvestmentPortfolioController::visible()->whereKey($data['from_dossier'])->firstOrFail() : null;
if ($dossierOrigin) {
$linked = false;
$linked = in_array($record->id, app(\App\Services\CorridorAgribusinessOpportunities::class)->portfolioOpportunityIds($dossierOrigin), true);
foreach (['projects' => 'project_id', 'clusters' => 'cluster_id', 'corridors' => 'corridor_id'] as $relation => $foreign) {
if ($dossierOrigin->$foreign && DB::table('opportunity_'.$relation)->where('opportunity_id', $record->id)->where($foreign, $dossierOrigin->$foreign)->exists()) {
$linked = true;
@@ -105,6 +105,7 @@ class InvestmentPortfolioController extends Controller
$opportunities = Opportunity::published()->where(function ($query) use ($record) {
$query->whereRaw('false');
$query->orWhereIn('id', app(\App\Services\CorridorAgribusinessOpportunities::class)->portfolioOpportunityIds($record));
foreach (['clusters' => 'cluster_id', 'corridors' => 'corridor_id', 'projects' => 'project_id'] as $relation => $foreign) {
if ($record->$foreign) {
$query->orWhereIn('id', DB::table('opportunity_'.$relation)->where($foreign, $record->$foreign)->select('opportunity_id'));
@@ -11,6 +11,19 @@ use Inertia\Inertia;
final class InvestorContextController extends Controller
{
private function entryChoices(Request $r, InvestorContext $context): array
{
$data = $r->validate(['source' => 'nullable|string|max:100', 'from_dossier' => 'nullable|uuid', 'opportunities' => 'sometimes|array|max:100', 'opportunities.*' => 'integer|distinct|min:1',
'province_id' => 'nullable|uuid', 'district_id' => 'nullable|uuid', 'admin_post_id' => 'nullable|uuid', 'locality_id' => 'nullable|uuid']);
$source = !empty($data['source']) ? $context->reference($data['source']) : null;
$ids = $data['opportunities'] ?? (($source['kind'] ?? '') === 'opportunity' ? [$source['id']] : []);
abort_if(!empty($data['from_dossier']) && !$ids, 422, 'Escolha a oportunidade associada à ficha da carteira.');
$path = app(InvestmentGeography::class)->selection($data);
$input = array_map(fn($id) => ['source' => 'opportunity:'.$id, ...($path ? ['territory' => 'territory:'.$path[array_key_last($path)]->id] : [])], $ids);
if (!empty($data['from_dossier'])) $input[0]['portfolio_origins'] = [$data['from_dossier']];
return app(\App\Services\InvestmentOpportunityChoices::class)->resolve($input, [], true);
}
public function current(Request $r, InvestorContext $context)
{
return response()->json($context->current($r->user()));
@@ -33,12 +46,45 @@ final class InvestorContextController extends Controller
return response()->json($context->options($d['q'] ?? ''));
}
public function corridorOptions(Request $r)
{
$data = $r->validate(['corridor' => ['nullable', \Illuminate\Validation\Rule::in(array_keys(config('investment_corridors')))], 'territory' => 'nullable|uuid']);
$service = app(\App\Services\InvestmentCorridorContext::class);
return response()->json(['corridors' => collect(config('investment_corridors'))->map(fn($entry,$key)=>['key'=>$key,'name'=>$entry['name'],'provinces'=>$service->provinces($key)])->values(),
'parks' => $service->parks($data['corridor'] ?? null, $data['territory'] ?? null)]);
}
public function saveDraft(Request $r, string $id, InvestorContext $context)
{
return response()->json($context->saveDraft($r->user(), $id, $r->all()));
}
public function edit(Request $r, string $id, InvestorContext $context)
{
$data = $context->view($r->user(), $id);
abort_unless($data['can_write'], 403);
return Inertia::render('investor-path/context', ['initial' => $data, 'options' => $context->options(), 'source' => $r->query('source') ? $context->reference($r->validate(['source' => 'required|string|max:100'])['source']) : null, 'territorySelection' => isset($data['context']['selection']['territory']) ? app(InvestmentGeography::class)->path($data['context']['selection']['territory']['id']) : []]);
$entries = $this->entryChoices($r, $context);
$choices = $entries ?: null;
$source = $r->query('source') ? $context->reference($r->validate(['source' => 'required|string|max:100'])['source']) : null;
if ($choices === null && ($source['kind'] ?? '') === 'opportunity') $choices = app(\App\Services\InvestmentOpportunityChoices::class)->resolve([['source' => $source['key']]]);
$explicitChoices=$choices;
$selectionKey=$r->validate(['selection_key'=>'nullable|uuid'])['selection_key']??null;
$entryKey=fn($c)=>$c['opportunity']['key'].($selectionKey?'@'.$selectionKey:'');
$entrySources=array_map($entryKey,$explicitChoices??[]);
if ($choices !== null) $choices = app(\App\Services\InvestmentOpportunityChoices::class)->mergeEntries($data['context']['opportunity_choices'] ?? [], $choices);
$draft = $context->draft($r->user(), $id);
if ($draft && isset($draft['opportunity_choices'])) {
$recovered = app(\App\Services\InvestmentOpportunityChoices::class)->resolve($draft['opportunity_choices'], $data['context']['opportunity_choices'] ?? [], true);
$new=array_filter($explicitChoices??[],fn($c)=>!in_array($entryKey($c),$draft['entry_sources']??[],true));
$choices = app(\App\Services\InvestmentOpportunityChoices::class)->mergeEntries($recovered, array_values($new));
}
$differences = [];
foreach ($explicitChoices ?? [] as $entry) {
$kept = collect($choices ?? [])->firstWhere('opportunity.key', $entry['opportunity']['key']);
if ($kept && ($kept['territory']['key'] ?? null) !== ($entry['territory']['key'] ?? null)) $differences[] = ['title'=>$entry['opportunity']['title'],'saved'=>$kept['territory']['title']??'Por definir','requested'=>$entry['territory']['title']??'Por definir'];
}
return Inertia::render('investor-path/context', ['entryLocationDifferences'=>$differences,'entrySources'=>$entrySources,'draft' => $draft, 'requestedChoices' => $choices, 'initial' => $data, 'options' => $context->options(), 'source' => $source, 'territorySelection' => isset($data['context']['selection']['territory']) ? app(InvestmentGeography::class)->path($data['context']['selection']['territory']['id']) : []]);
}
public function preview(Request $r, string $id, InvestorContext $context)
@@ -56,7 +102,8 @@ final class InvestorContextController extends Controller
$d = $r->validate(['source' => 'nullable|string|max:100', 'step' => ['nullable', 'regex:/^S(0[1-9]|1[0-9])$/']]);
$ref = isset($d['source']) ? $context->reference($d['source']) : null;
return Inertia::render('investor-path/context-start', ['contexts' => $path->contexts($r->user()), 'source' => $ref, 'step' => $d['step'] ?? null]);
$choices = $this->entryChoices($r, $context);
return Inertia::render('investor-path/context-start', ['entryQuery' => $r->only(['from_dossier', 'province_id', 'district_id', 'admin_post_id', 'locality_id']), 'opportunityChoices' => $choices, 'contexts' => $path->contexts($r->user()), 'source' => $ref, 'step' => $d['step'] ?? null]);
}
public function installation(Request $r, InvestorContextInstallation $install)
@@ -103,6 +103,7 @@ class OpportunityManagementController extends Controller
$records = $query->orderByDesc('id')->paginate(20)->withQueryString();
$records->getCollection()->each(function (Opportunity $r) use ($access, $service, $request) {
$r->setAttribute('can_edit', $access->canEdit($request->user(), $r));
$r->setAttribute('can_promote', app(\App\Services\OpportunityPromotion::class)->allowed($request->user(), $r));
$r->setAttribute('working_status', $access->state($r));
$r->setAttribute('publication', $r->publicationPresentation());
$r->setAttribute('draft_title', $service->values($r)['canonical']['official_title'] ?? $r->title);
@@ -130,6 +131,7 @@ class OpportunityManagementController extends Controller
'profileReauthenticationRequired' => $record && $record->owner_user_id !== null
? app(OpportunityProfileWorkflow::class)->reauthenticationRequired($request->user(), $record) : false,
'publicationState' => $record?->publicationPresentation(),
'promotionHref' => $record && app(\App\Services\OpportunityPromotion::class)->allowed($request->user(), $record) ? '/minhas-oportunidades/'.$record->id.'/divulgacao' : null,
'canonicalResponse' => $record?->owner_user_id !== null ? app(OpportunityCanonicalResponse::class)->read($request->user(), $record) : null,
'canEditDraft' => $record ? $access->canEdit($request->user(), $record) : false,
'workflowParticipants' => $record && $record->owner_user_id !== null ? app(OpportunityProfileWorkflow::class)->participants($request->user(), $record) : ['validators' => [], 'publishers' => []],
@@ -0,0 +1,26 @@
<?php
namespace App\Http\Controllers;
use App\Models\Opportunity;
use App\Services\OpportunityPromotion;
use Illuminate\Http\Request;
use Inertia\Inertia;
final class OpportunityPromotionController extends Controller
{
public function show(Request $request, int $id, OpportunityPromotion $service): \Inertia\Response
{
return Inertia::render('management/opportunity-promotion', $service->editor($request->user(), Opportunity::findOrFail($id)));
}
public function preview(Request $request, int $id, OpportunityPromotion $service): \Illuminate\Http\JsonResponse
{
return response()->json($service->preview($request->user(), Opportunity::findOrFail($id), $request->all()));
}
public function confirm(Request $request, int $id, OpportunityPromotion $service): \Illuminate\Http\JsonResponse
{
return response()->json($service->confirm($request->user(), $id, $request->all()));
}
}
@@ -11,8 +11,13 @@ class PublicGeographyController extends Controller
{
public function options(Request $request, InvestmentGeography $geography): JsonResponse
{
$data = $request->validate(['type' => ['required', 'in:province,district,admin_post,locality'], 'id' => ['nullable', 'uuid'], 'parent_id' => ['nullable', 'uuid'], 'q' => ['nullable', 'string', 'max:100'], 'page' => ['nullable', 'integer', 'min:1']]);
$data = $request->validate(['corridor' => ['nullable', \Illuminate\Validation\Rule::in(array_keys(config('investment_corridors')))], 'type' => ['required', 'in:province,district,admin_post,locality'], 'id' => ['nullable', 'uuid'], 'parent_id' => ['nullable', 'uuid'], 'q' => ['nullable', 'string', 'max:100'], 'page' => ['nullable', 'integer', 'min:1']]);
$query = $geography->publicReferences()->where('location_type', $data['type']);
if (!empty($data['corridor'])) {
$corridors = app(\App\Services\InvestmentCorridorContext::class);
if ($data['type'] === 'province') $query->whereIn('id', array_column($corridors->provinces($data['corridor']), 'id'));
elseif (!$corridors->compatible($data['corridor'], $data['parent_id'] ?? null)) abort(422, 'O território não pertence ao corredor escolhido.');
}
if (! empty($data['id'])) {
$query->where('id', $data['id']);
}
@@ -29,6 +34,6 @@ class PublicGeographyController extends Controller
->orWhereRaw('source_code ILIKE ?', [$term]));
}
return response()->json($query->orderBy('name')->orderBy('id')->paginate(100, ['id', 'name', 'parent_id', 'location_type', 'source_code']));
return response()->json($query->orderBy('name')->orderBy('id')->paginate(100, ['id', 'name', 'parent_id', 'location_type', 'source_code'])->withQueryString());
}
}
@@ -29,7 +29,9 @@ class ProtectProfileOpportunity
if ($record && ($record->owner_user_id !== null || str_starts_with($request->path(), 'minhas-oportunidades'))) {
abort_if($record->owner_user_id === null && str_starts_with($request->path(), 'minhas-oportunidades'), 403, 'Registo legado não convertido para a política v1.');
$access = app(PortalProfileAccess::class);
$access->authorize($request->user(), $request->isMethod('GET') || str_ends_with($request->path(), '/workflow') || str_ends_with($request->path(), '/preview') || $request->is('minhas-oportunidades/*/classification/review') ? 'view' : 'edit', $record);
// The promotional controller checks its own scoped disclosure capability,
// like workflow decisions; publication does not grant draft editing.
$access->authorize($request->user(), $request->isMethod('GET') || str_ends_with($request->path(), '/workflow') || str_ends_with($request->path(), '/preview') || $request->is('minhas-oportunidades/*/classification/review', 'minhas-oportunidades/*/divulgacao/previa', 'minhas-oportunidades/*/divulgacao/confirmar') ? 'view' : 'edit', $record);
abort_if($request->is('management/opportunity-origin/apply'), 403, 'O cadastro privado v1 não usa a classificação editorial dos exemplos legados.');
abort_if($request->isMethod('POST') && str_ends_with($request->path(), '/lifecycle') && ! str_contains($request->path(), '/resources/'), 403, 'Use o workflow com decisões segregadas; o perfil não concede exclusão.');
}
@@ -72,7 +72,7 @@ class StoreSimulationFormDraftRequest extends FormRequest
'economic_snapshot.business_plan_snapshot' => ['nullable', 'string', 'max:60000'],
'economic_snapshot.financial_tools_snapshot' => ['nullable', 'string', 'max:30000'],
'economic_snapshot.cashflow_snapshot' => ['nullable', 'string', 'max:20000'],
'economic_snapshot.context_applicability_snapshot' => ['nullable', 'string', 'max:20000'],
'economic_snapshot.context_applicability_snapshot' => ['nullable', 'string', 'max:1000000'],
'economic_snapshot.investment_regime' => ['nullable', 'in:general,industrial_park,zee,zfi'],
'economic_snapshot.context_reuse' => ['nullable', 'string', 'max:10000'],
'economic_snapshot.compatible_fields_snapshot' => ['nullable', 'string', 'max:10000'],
+9 -1
View File
@@ -31,6 +31,13 @@ class Opportunity extends Model
protected $appends = ['maturity_catalogue_version'];
public function toArray(): array
{
$data = parent::toArray();
if (isset($data['description'])) $data['description'] = \App\Services\OpportunityPublicText::description($this, $data['description']);
return $data;
}
public function getMaturityCatalogueVersionAttribute(): ?string
{
// Only published metadata is public; never expose the current private draft.
@@ -68,7 +75,8 @@ class Opportunity extends Model
$scope = $public ? 'public' : ($visible && $this->owner_user_id !== null ? 'restricted' : 'unpublished');
return ['scope' => $scope, 'public_catalog' => $public, 'label' => match ($scope) {
'public' => 'Publicada no catálogo público',
'public' => data_get($this->workflow_context, 'publication.purpose') === 'promotional_preparation'
? 'Resumo divulgado — Em preparação; não autoriza execução' : 'Publicada no catálogo público',
'restricted' => 'Disponibilizada com acesso restrito — perfis e organização autorizados',
default => 'Sem versão disponibilizada; aprovação interna é uma etapa distinta',
}];
+83
View File
@@ -0,0 +1,83 @@
<?php
namespace App\Services;
use DOMDocument;
use DOMElement;
/** Supplement the six named agribusiness sections without changing corridor masters or source documents. */
final class CorridorAgribusiness
{
public function enrich(string $html, array $links = []): string
{
$data = json_decode(file_get_contents(__DIR__.'/../../database/data/corridor-investment-opportunities.json'), true, 512, JSON_THROW_ON_ERROR);
$doc = new DOMDocument;
$previous = libxml_use_internal_errors(true);
try {
$doc->loadHTML('<?xml encoding="UTF-8"><html><body>'.$html.'</body></html>', LIBXML_NONET);
$body = $doc->getElementsByTagName('body')->item(0);
if (! $body) {
return $html;
}
$changed = false;
foreach ($data['groups'] as $index => $group) {
$marker = 'agronegocio-oportunidades-'.($index + 1);
if ($doc->getElementById($marker)) {
continue;
}
foreach (iterator_to_array($body->childNodes) as $heading) {
if (! $heading instanceof DOMElement) {
continue;
}
$name = preg_replace('/^[a-f]\)\s*/u', '', trim($heading->textContent));
if ($name !== $group['name']) {
continue;
}
$chains = $heading->nextSibling;
while ($chains && ! $chains instanceof DOMElement) {
$chains = $chains->nextSibling;
}
// Insert only into the exact named section and its existing priority-chain paragraph.
if (! $chains || ! str_starts_with(trim($chains->textContent), 'Cadeias de valor prioritárias:')) {
continue;
}
$fragment = $doc->createDocumentFragment();
$label = $doc->createElement('p');
$strong = $doc->createElement('strong');
$strong->appendChild($doc->createTextNode('Oportunidades de investimento em agronegócio:'));
$label->appendChild($strong);
$fragment->appendChild($label);
$list = $doc->createElement('ul');
$list->setAttribute('id', $marker);
foreach ($group['opportunities'] as $itemIndex => $opportunity) {
$item = $doc->createElement('li');
$chain = $doc->createElement('strong');
$chain->appendChild($doc->createTextNode($opportunity['chain'].': '));
$item->appendChild($chain);
$item->appendChild($doc->createTextNode($opportunity['description']));
if (isset($links[$index][$itemIndex])) {
$item->appendChild($doc->createTextNode(' '));
$link = $doc->createElement('a');
$link->setAttribute('href', $links[$index][$itemIndex]);
$link->appendChild($doc->createTextNode('Conhecer a oportunidade e iniciar o roteiro'));
$item->appendChild($link);
}
$list->appendChild($item);
}
$fragment->appendChild($list);
$source = $doc->createElement('p');
$source->appendChild($doc->createTextNode('Fonte das oportunidades: '.$data['source']));
$fragment->appendChild($source);
$body->insertBefore($fragment, $chains->nextSibling);
$changed = true;
break;
}
}
return $changed ? implode('', array_map(fn ($node) => $doc->saveHTML($node), iterator_to_array($body->childNodes))) : $html;
} finally {
libxml_clear_errors();
libxml_use_internal_errors($previous);
}
}
}
@@ -0,0 +1,97 @@
<?php
namespace App\Services;
use App\Models\Opportunity;
use App\Models\Sector;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Ramsey\Uuid\Uuid;
/** Source-defined opportunities; never infer a master corridor, site or promoter. */
final class CorridorAgribusinessOpportunities
{
public const SOURCE_HASH = '1473b06c48aa234e771660e3f2a51b9d2e4e1943bd52d878e0ae854ee6657055';
/** Documentary cross-reference only; keep the two source designations and master records intact. */
public function portfolioOpportunityIds(\App\Models\InvestmentDossier $dossier): array
{
if (!$dossier->corridor_id || data_get($dossier->content, 'code') !== 'COR-02') return [];
// COR-02 is Beira e Centro in investment-portfolio.json. The seven entries are explicitly
// grouped under Corredor da Beira in the accepted source, not selected by province/name search.
$keys = collect($this->entries())->where('corridor', 'Corredor da Beira')->pluck('key');
return Opportunity::published()->whereIn('management_creation_key', $keys)
->where('workflow_context->corridor_agribusiness->source_sha256', self::SOURCE_HASH)->pluck('id')->all();
}
public const LIMITATIONS = 'Promotor, implantação exacta, área, montante, financiamento, viabilidade e licenças não indicados nesta fonte. O corredor é um enquadramento documental; não identifica uma parcela nem confirma disponibilidade.';
public function entries(): array
{
$data = json_decode(file_get_contents(database_path('data/corridor-investment-opportunities.json')), true, 512, JSON_THROW_ON_ERROR);
if ($data['source_sha256'] !== self::SOURCE_HASH) throw new \RuntimeException('Fonte divergente; reconciliar antes de cadastrar.');
$sector = Sector::where('slug', 'agronegocio')->where('is_active', true)->whereNull('deleted_at')->firstOrFail();
$entries = [];
foreach ($data['groups'] as $g => $group) {
foreach ($group['opportunities'] as $i => $item) {
$key = (string) Uuid::uuid5(Uuid::NAMESPACE_URL, 'investmoz:agronegocio:'.self::SOURCE_HASH.':'.$g.':'.$i);
$title = Str::limit($item['chain'].' — '.$group['name'], 150, '…');
$description = $item['description']."\n\nEnquadramento documental: ".$group['name'].".\n\nFonte: ".$data['source'].'; SHA-256 '.self::SOURCE_HASH.".\n\n".self::LIMITATIONS;
$entries[] = ['key' => $key, 'group' => $g, 'index' => $i, 'corridor' => $group['name'], 'chain' => $item['chain'],
'source' => $data['source'], 'source_sha256' => self::SOURCE_HASH, 'title' => $title,
'summary' => $item['description'], 'description' => $description, 'sector_id' => $sector->id];
}
}
if (count($entries) !== 34) throw new \RuntimeException('O lote deve conter as 34 entradas da fonte.');
return $entries;
}
public function register(User $actor, string $organization): array
{
// The caller uses the ordinary profile creation route and its approved scope.
abort_unless(str_starts_with(request()->path(), 'minhas-oportunidades'), 422);
return DB::transaction(function () use ($actor, $organization) {
DB::select('SELECT pg_advisory_xact_lock(231026, 34)');
$result = [];
foreach ($this->entries() as $entry) {
$record = Opportunity::withTrashed()->where('management_creation_key', $entry['key'])->first();
if ($record) {
abort_unless(app(PortalProfileAccess::class)->canView($actor, $record), 403);
abort_unless(data_get($record->workflow_context, 'corridor_agribusiness.source_sha256') === self::SOURCE_HASH, 409);
// Recovery never overwrites editorial changes, withdrawal or archive.
$result[] = ['id' => $record->id, 'created' => false, 'key' => $entry['key']];
continue;
}
$record = app(OpportunityManagement::class)->save($actor, ['creation_key' => $entry['key'], 'organization_id' => $organization,
'opportunity_category' => 'investment_project', 'sector_id' => $entry['sector_id'],
'title' => $entry['title'], 'summary' => $entry['summary'], 'description' => $entry['description'],
'canonical' => ['opportunity_category' => 'investment_project', 'official_title' => $entry['title'],
'executive_summary' => $entry['summary'], 'demand_statement' => $entry['description'], 'sector_id' => $entry['sector_id']],
'reason' => 'Cadastro privado das 34 oportunidades de agronegócio solicitado pela Tempo em 23/10/2026; não constitui publicação nem aprovação de execução.']);
$context = $record->workflow_context ?? [];
$context['corridor_agribusiness'] = $entry;
$context['promotional_summary'] = ['input' => ['title' => $entry['title'], 'summary' => $entry['summary'],
'description' => $entry['description'], 'source_reference' => $entry['source'].'; SHA-256 '.self::SOURCE_HASH,
'limitations' => self::LIMITATIONS, 'edition' => '', 'source_date' => '', 'territory_ids' => [], 'sector_id' => $entry['sector_id']]];
$record->workflow_context = $context;
$record->maturity_label = 'Em preparação';
$record->save();
DB::table('audit_events')->insert(['source_system' => 'corridor-agribusiness-registration', 'entity_type' => 'opportunity',
'action' => 'private_source_registered', 'actor_user_id' => $actor->id, 'data_classification' => 'restricted',
'after_data' => json_encode(['record_id' => $record->id, 'source' => $entry, 'no_publication' => true, 'no_master_relation_inferred' => true], JSON_THROW_ON_ERROR)]);
$result[] = ['id' => $record->id, 'created' => true, 'key' => $entry['key']];
}
return $result;
});
}
public function articleLinks(): array
{
$records = Opportunity::published()->whereIn('management_creation_key', array_column($this->entries(), 'key'))->get()->keyBy('management_creation_key');
$links = [];
foreach ($this->entries() as $entry) {
if ($record = $records->get($entry['key'])) $links[$entry['group']][$entry['index']] = '/oportunidades/'.$record->slug;
}
return $links;
}
}
+17 -1
View File
@@ -26,7 +26,7 @@ class InvestmentCatalogue
'cluster_id' => ['nullable', 'uuid'], 'corridor_id' => ['nullable', 'uuid'], 'project_id' => ['nullable', 'uuid'],
'geo_location_id' => ['nullable', 'uuid'], 'education' => ['nullable', 'boolean'],
'zee_id' => ['nullable', 'uuid'], 'zfi_id' => ['nullable', 'uuid'], 'park_id' => ['nullable', 'uuid'],
'dimension' => ['nullable', 'in:cluster,corridor,zee,zfi'], 'view' => ['nullable', 'in:grid,list,map,combined'],
'dimension' => ['nullable', 'in:cluster,corridor,zee,zfi'], 'view' => ['nullable', 'in:grid,list,map,combined,corridors'],
'maturity_level' => ['nullable', 'integer', 'between:1,8'], 'facets' => ['nullable', 'array', 'max:20'], 'facets.*' => ['integer', 'min:1'],
'min_amount' => ['nullable', 'numeric', 'min:0', 'max:999999999999'],
'max_amount' => ['nullable', 'numeric', 'min:0', 'max:999999999999', ...($request->filled('min_amount') ? ['gte:min_amount'] : [])],
@@ -292,6 +292,10 @@ class InvestmentCatalogue
}
}
});
if (!empty($selected['corridor_id'])) {
$dossier = InvestmentPortfolioController::visible()->where('corridor_id', $selected['corridor_id'])->first();
if ($dossier) $ids['corridor_id'] = array_values(array_unique([...($ids['corridor_id'] ?? []), ...app(CorridorAgribusinessOpportunities::class)->portfolioOpportunityIds($dossier)]));
}
return $ids;
}
@@ -308,6 +312,18 @@ class InvestmentCatalogue
/** @return array<string,mixed> */
public function context(int $id, bool $management = false): array
{
$opportunity = Opportunity::findOrFail($id);
if (! $management && data_get($opportunity->workflow_context, 'publication.purpose') === OpportunityPromotion::PURPOSE) {
// Promotional disclosure includes only its explicit public coverage.
// Private profiles, source evidence and other dossier relations are not auto-published.
$public = ['profile' => null, 'facets' => collect(), 'zones' => collect(), 'classification' => [], 'source_evidence' => [],
'primary_territory_path' => app(InvestmentGeography::class)->path($opportunity->primary_territory_id, true),
'opportunity_locations' => app(InvestmentRelations::class)->opportunityLocations($id)];
foreach (self::RELATIONS as $relation => $_) $public[$relation] = collect();
$public['locations'] = DB::table('opportunity_locations as l')->join('geo_locations as g', 'g.id', '=', 'l.geo_location_id')
->where('l.opportunity_id', $id)->where('g.data_classification', 'public')->whereNull('g.deleted_at')->get(['g.id', 'g.name']);
return $public;
}
$result = ['profile' => DB::table('opportunity_profiles')->where('opportunity_id', $id)->first(),
'facets' => DB::table('opportunity_facets as f')->join('investment_reference_options as o', 'o.id', '=', 'f.option_id')
->where('f.opportunity_id', $id)->get(['o.id', 'o.group', 'o.label'])];
@@ -0,0 +1,48 @@
<?php
namespace App\Services;
/** Preparation within the existing path; no licence, DUAT or fiscal benefit is granted. */
final class InvestmentContextRequirements
{
public const VERSION = 'context-requirements-20261023';
public function assess(array $context): array
{
$rows = [];
foreach ($context['opportunity_choices'] ?? [] as $choice) {
$activities = $choice['activities'] ?? [];
$key = $choice['opportunity']['key'];
$common = ['opportunity_key' => $key, 'opportunity_title' => $choice['opportunity']['title']];
$add = function ($code, $title, $state, $step, $authority, $documents, $basis, $url) use (&$rows, $common) {
$rows[] = [...$common, 'code' => $code, 'title' => $title, 'state' => $state, 'step' => $step,
'authority' => $authority, 'documents' => $documents, 'basis' => $basis, 'source_url' => $url,
'decision' => 'Preparação; enquadramento e acto concreto conservam confirmação na etapa correspondente.'];
};
$add('geography', 'Localização e geolocalização', $choice['territory'] ? 'selected' : 'missing', 'S09', 'Cadastro territorial e serviços de cadastro competentes',
['Referência administrativa confirmada', 'Planta/esboço e coordenadas quando disponíveis; não inventar polígonos'],
'Referência territorial escolhida no dossier; não comprova disponibilidade nem direito de uso.', '/investimentos');
$add('land', 'Acesso à terra e DUAT', 'conditional', 'S10', 'Serviços de cadastro e órgão competente para o pedido territorial concreto',
['Fundamento do uso: DUAT, arrendamento ou situação declarada', 'Planta/esboço do local; documentação do pedido correspondente'],
'Regulamento de Investimento, artigos 9(1)(c)–(d) e 14(1)(c)–(d). A preparação distingue DUAT e arrendamento; escolher território não atribui terra.', PrivateInvestmentPreparation::SOURCE.'#page=42');
$add('environment', 'Enquadramento ambiental', 'conditional', 'S08', 'Autoridade de Avaliação do Impacto Ambiental; competência central/provincial conforme categorização',
['Descrição da actividade, dimensão e localização', 'Instrumento ambiental definido pela categorização competente; acto e condições quando emitidos'],
'Decreto 54/2015: a natureza, dimensão e localização determinam o enquadramento; a categoria não é deduzida do corredor ou do sector.', 'https://sibmoz.gov.mz/legal-framework/');
$add('sector', 'Licenciamento sectorial', $activities ? 'declared' : 'missing', 'S08', 'BAU, IP e autoridade sectorial competente conforme a actividade',
['Actividades efectivas e modalidade', 'Requerimento e anexos da modalidade aplicável; acto quando emitido'],
'Identificar as actividades efectivas antes de seleccionar o procedimento. O nome Agronegócio não escolhe uma licença única.', 'https://www.bau.gov.mz/nossos-servicos/');
foreach (['processing' => ['Licenciamento industrial', 'Decreto 22/2014'], 'trade' => ['Licenciamento comercial', 'Decreto 34/2013']] as $activity => [$title, $basis]) {
if (in_array($activity, $activities, true)) $add($activity, $title, 'conditional', 'S08', 'BAU, IP / entidade licenciadora da modalidade',
['Caracterização do estabelecimento e actividade', 'Documentos e regime aplicáveis, incluindo simplificado ou mera comunicação quando preenchidas as suas condições'],
$basis.'; regimes 39/2017 e 27/2023 no catálogo oficial BAU. Dimensão e modalidade ainda devem ser especificadas; não se selecciona automaticamente o regime.', 'https://www.bau.gov.mz/legislacao/');
}
foreach (['water' => 'Uso de água', 'forestry' => 'Actividade florestal', 'animal' => 'Actividade pecuária', 'transport' => 'Transporte e logística'] as $activity => $title) {
if (in_array($activity, $activities, true)) $add($activity, $title, 'undetermined', 'S08', 'Autoridade sectorial competente a identificar para a modalidade declarada',
['Modalidade e dimensão da actividade', 'Fonte e documentação sectorial específica a associar ao dossier'],
'Actividade declarada; a referência do lote não contém o procedimento específico. Não se considera dispensada ou autorizada.', 'https://www.apiex.gov.mz/wp-content/uploads/2024/01/Mozambique-Business-Licensing-Guide.pdf');
}
}
return ['version' => self::VERSION, 'requirements' => $rows,
'fiscal_notice' => 'Localização, corredor, cluster ou projecto âncora não activam benefícios fiscais. O simulador mantém as condições, fonte, período e âmbito próprios; condições ausentes permanecem indeterminadas.'];
}
}
@@ -0,0 +1,59 @@
<?php
namespace App\Services;
use App\Models\Opportunity;
use Illuminate\Support\Facades\DB;
final class InvestmentCorridorContext
{
public function key(Opportunity $record): ?string
{
$name = data_get($record->workflow_context, 'corridor_agribusiness.corridor');
foreach (config('investment_corridors') as $key => $entry) if ($entry['name'] === $name) return $key;
return null;
}
public function provinces(string $key): array
{
$entry = config('investment_corridors.'.$key);
abort_unless(is_array($entry), 422, 'Seleccione um corredor disponível.');
$records = app(InvestmentGeography::class)->publicReferences()->where('location_type', 'province')
->whereIn('source_code', $entry['provinces'])->get(['id', 'name', 'parent_id', 'location_type', 'source_code'])->all();
abort_unless(count($records) === count($entry['provinces']), 503, 'Não foi possível recuperar todas as províncias deste corredor. Tente novamente.');
return $records;
}
public function compatible(string $key, ?string $territory): bool
{
if (!$territory) return true;
$path = app(InvestmentGeography::class)->path($territory);
return $path !== [] && in_array($path[0]->id, array_column($this->provinces($key), 'id'), true);
}
/** Both membership in the selected area and documented public zone coverage are required. */
public function parks(?string $key, ?string $territory): array
{
if (!$key || !$territory) return [];
abort_unless($this->compatible($key, $territory), 422, 'A localização não pertence ao corredor escolhido.');
$selectedPath = array_column(app(InvestmentGeography::class)->path($territory), 'id');
$candidates = app(InvestmentZones::class)->visible()->whereIn('category', ['industrial_park', 'zee', 'zfi'])->orderBy('name')->get();
$result = [];
foreach ($candidates as $zone) {
$ids = DB::table('investment_zone_locations')->where('zone_id', $zone->id)->where('validation_status', 'validated')
->whereIn('scope_type', ['legal_scope', 'administrative', 'site'])
->where(fn($q)=>$q->whereNull('valid_from')->orWhereDate('valid_from','<=',today()))
->where(fn($q)=>$q->whereNull('valid_until')->orWhereDate('valid_until','>=',today()))->get(['geo_location_id','scope_type'])->all();
if ($zone->primary_territory_id) $ids[] = (object)['geo_location_id'=>$zone->primary_territory_id,'scope_type'=>'site'];
foreach ($ids as $link) {
$id=$link->geo_location_id;
if (!$this->compatible($key, $id)) continue;
$path = array_column(app(InvestmentGeography::class)->path($id), 'id');
if (in_array($territory, $path, true) || ($link->scope_type==='legal_scope' && in_array($id, $selectedPath, true))) {
$result[] = $zone->only(['id', 'name', 'category', 'version_no']); break;
}
}
}
return $result;
}
}
@@ -0,0 +1,82 @@
<?php
namespace App\Services;
use App\Http\Controllers\InvestmentPortfolioController;
use App\Models\Opportunity;
use Illuminate\Support\Facades\DB;
/** Multiple identified references in the existing encrypted investment context. */
final class InvestmentOpportunityChoices
{
public function portfolioOrigins(array $ids, int $opportunity): array
{
$origins = [];
foreach (array_unique($ids) as $id) {
$record = InvestmentPortfolioController::visible()->addSelect('version_no')->findOrFail($id);
$kind = null;
if (in_array($opportunity, app(CorridorAgribusinessOpportunities::class)->portfolioOpportunityIds($record), true)) $kind = 'corridor';
foreach (['projects' => 'project_id', 'clusters' => 'cluster_id', 'corridors' => 'corridor_id'] as $relation => $foreign) {
if ($record->$foreign && DB::table('opportunity_'.$relation)->where('opportunity_id', $opportunity)->where($foreign, $record->$foreign)->exists()) $kind = rtrim($relation, 's');
}
abort_unless($kind, 422, 'A associação escolhida não pertence a esta oportunidade.');
$origins[] = ['id' => $record->id, 'title' => $record->title, 'kind' => $kind, 'href' => '/carteira/'.$record->id,
'version' => $record->version_no, 'sha256' => hash('sha256', json_encode([$record->title, $record->source, $record->content], JSON_THROW_ON_ERROR))];
}
return $origins;
}
/** Add explicitly consulted associations without resetting saved geography or deliberate answers. */
public function mergeEntries(array $saved, array $entries): array
{
foreach ($entries as $entry) {
$index = array_search($entry['opportunity']['key'], array_column(array_column($saved, 'opportunity'), 'key'), true);
if ($index === false) { $saved[] = $entry; continue; }
$origins = collect([...($saved[$index]['portfolio_origins'] ?? []), ...($entry['portfolio_origins'] ?? [])])->unique('id')->values()->all();
if ($origins) $saved[$index]['portfolio_origins'] = $origins;
}
return $saved;
}
public function resolve(array $choices, array $previous = [], bool $pending = false): array
{
$result = [];
foreach ($choices as $choice) {
$ref = app(InvestorContext::class)->reference($choice['source']);
abort_unless($ref['kind'] === 'opportunity', 422, 'Seleccione uma ficha de oportunidade.');
$record = Opportunity::findOrFail($ref['id']);
$old = collect($previous)->first(fn ($row) => $row['opportunity']['key'] === $ref['key']);
$origins = $this->portfolioOrigins($choice['portfolio_origins'] ?? array_column($old['portfolio_origins'] ?? [], 'id'), (int) $record->id);
$corridors = app(InvestmentCorridorContext::class);
$corridor = $choice['corridor_key'] ?? $old['corridor_key'] ?? $corridors->key($record);
$provinces = $corridor ? $corridors->provinces($corridor) : [];
$documented = $record->primary_territory_id;
$territory = $choice['territory'] ?? ($documented ? 'territory:'.$documented : null);
if (!$territory && count($provinces) === 1) $territory = 'territory:'.$provinces[0]->id;
if ($corridor && $territory) abort_unless($corridors->compatible($corridor, substr($territory, 10)), 422, 'A localização não pertence ao corredor escolhido.');
$previousTerritory = $old['territory']['key'] ?? null;
$newAlternative = $documented && $territory !== 'territory:'.$documented
&& (! $old || ($old['documented_territory_id'] ?? null) !== $documented || $territory !== $previousTerritory);
$corridorChanged = $old && ($old['corridor_key'] ?? $corridors->key($record)) !== $corridor;
$incompatibleOld = $corridorChanged && $previousTerritory && !$corridors->compatible($corridor, substr($previousTerritory, 10));
if (!$pending && ($newAlternative || ($previousTerritory && $territory !== $previousTerritory && !$incompatibleOld))) {
abort_unless(! empty($choice['confirm_location_change']) && mb_strlen(trim($choice['location_reason'] ?? '')) >= 20,
422, 'Uma localização documentada ou já confirmada só muda por escolha explícita, com confirmação e motivo.');
}
$location = $territory ? app(InvestorContext::class)->reference($territory) : null;
abort_unless($location === null || $location['kind'] === 'territory', 422);
$result[] = ['opportunity' => $ref, 'territory' => $location, ...($origins ? ['portfolio_origins' => $origins] : []),
'corridor_key' => $corridor, 'corridor_name' => $corridor ? config('investment_corridors.'.$corridor.'.name') : null,
'allowed_provinces' => array_map(fn($p)=>(array)$p, $provinces),
'territory_path' => $location ? array_map(fn ($node) => \Illuminate\Support\Arr::only((array) $node, ['id', 'name', 'parent_id', 'location_type']), app(InvestmentGeography::class)->path($location['id'], true)) : [],
'activities' => $choice['activities'] ?? ($old['activities'] ?? []),
'documented_territory_id' => $documented, 'location_reason' => $choice['location_reason'] ?? '',
'location_state' => $location ? ($documented && $territory === 'territory:'.$documented ? 'documented' : 'investor_selected') : 'undetermined',
'location_changed_explicitly' => $old && $territory === $previousTerritory ? (bool) ($old['location_changed_explicitly'] ?? false) : (bool) ($choice['confirm_location_change'] ?? false),
'coverage' => data_get($record->workflow_context, 'corridor_agribusiness.corridor'),
'source' => data_get($record->workflow_context, 'corridor_agribusiness.source'),
'source_sha256' => data_get($record->workflow_context, 'corridor_agribusiness.source_sha256')];
}
return $result;
}
}
+145 -7
View File
@@ -27,6 +27,65 @@ final class InvestorContext
public function __construct(private readonly VirtualDesk $desk) {}
public function draft(User $actor, string $id): ?array
{
$case = $this->desk->case($actor, $id, true);
$draft = $this->desk->decode($case->payload)['investor_path']['context_drafts'][(string)$actor->id] ?? null;
return $draft ? [...$draft, 'needs_review' => (int)$draft['version'] !== (int)$case->version] : null;
}
public function saveDraft(User $actor, string $id, array $input): array
{
$case=$this->desk->case($actor,$id,true);
abort_if($case->closed_at,409);
$sequence = (int)($input['draft_sequence'] ?? 0);
$entrySources=Validator::make($input,['entry_sources'=>'sometimes|array|max:100','entry_sources.*'=>'string|max:100'])->validate()['entry_sources']??[];
unset($input['draft_sequence'],$input['entry_sources']);
abort_unless($sequence > 0, 422);
$data = $this->input($input);
foreach ($data['selection'] as $slot=>$key) {
if (!$key) continue;
$ref=$this->reference($key);
abort_unless(in_array($ref['kind'],$slot==='opportunity'?['opportunity','business']:[$slot],true),422,'Referência incompatível com a escolha.');
}
foreach ($data['opportunity_choices'] ?? [] as $choice) {
$ref = $this->reference($choice['source']);
abort_unless($ref['kind'] === 'opportunity', 422);
$record = Opportunity::findOrFail($ref['id']);
app(InvestmentOpportunityChoices::class)->portfolioOrigins($choice['portfolio_origins'] ?? [], (int) $record->id);
$key = $choice['corridor_key'] ?? app(InvestmentCorridorContext::class)->key($record);
if (!empty($choice['territory'])) {
$location = $this->reference($choice['territory']);
abort_unless($location['kind'] === 'territory', 422);
if ($key) abort_unless(app(InvestmentCorridorContext::class)->compatible($key, $location['id']), 422, 'A localização não pertence ao corredor escolhido.');
}
}
if (!empty($data['selection']['park'])) {
$first=$data['opportunity_choices'][0] ?? null;
$source=$first['source'] ?? $data['selection']['opportunity'] ?? null;
if ($source && str_starts_with($source,'opportunity:')) {
$key=$first['corridor_key'] ?? app(InvestmentCorridorContext::class)->key(Opportunity::findOrFail(substr($source,12)));
if ($key) {
$territory=$first['territory'] ?? $data['selection']['territory'] ?? null;
$ids=app(InvestmentCorridorContext::class)->provinces($key);
$territory=$territory ? substr($territory,10) : (count($ids)===1?$ids[0]->id:null);
$park=$this->reference($data['selection']['park']);
abort_unless($park['kind']==='park' && in_array($park['id'],array_column(app(InvestmentCorridorContext::class)->parks($key,$territory),'id'),true),422,'A zona ou parque não tem enquadramento compatível documentado nesta localização.');
}
}
}
return DB::transaction(function () use ($actor, $id, $data, $sequence, $entrySources) {
$case = $this->desk->case($actor, $id, true, true);
abort_if($case->closed_at || (int)$case->version !== (int)$data['version'], 409, 'O dossier mudou. Recupere os dados antes de continuar.');
$payload = $this->desk->decode($case->payload);
$previous = $payload['investor_path']['context_drafts'][(string)$actor->id] ?? [];
abort_if(($previous['draft_sequence'] ?? 0) > $sequence, 409, 'Existe um rascunho mais recente. Recupere o dossier antes de continuar.');
$payload['investor_path']['context_drafts'][(string)$actor->id] = [...$data, 'draft_sequence' => $sequence, 'entry_sources'=>array_values(array_unique([...($previous['entry_sources']??[]),...$entrySources]))];
DB::table('desk_cases')->where('id', $id)->update(['payload' => $this->desk->encode($payload)]);
return ['saved' => true];
});
}
public function installed(): bool
{
return Schema::hasTable('investor_navigation_preferences');
@@ -60,6 +119,7 @@ final class InvestorContext
public function dependencyHash(array $selection): string
{
if (isset($selection['opportunity_choices'])) $selection['opportunity_choices'] = array_map(fn($choice)=>Arr::except($choice,['portfolio_origins']),$selection['opportunity_choices']);
return hash('sha256', json_encode(Arr::sortRecursive($selection), JSON_THROW_ON_ERROR));
}
@@ -268,7 +328,7 @@ final class InvestorContext
private function input(array $input): array
{
abort_if(array_diff(array_keys($input), ['version', 'selection', 'declarations', 'replace_fields', 'preview_hash', 'operation_key', 'confirm', 'document_links', '_token']), 422, 'Campo de controlo desconhecido.');
abort_if(array_diff(array_keys($input), ['opportunity_choices', 'version', 'selection', 'declarations', 'replace_fields', 'preview_hash', 'operation_key', 'confirm', 'document_links', '_token']), 422, 'Campo de controlo desconhecido.');
abort_if(! app(PrivateInvestmentPreparation::class)->available() && (! empty($input['document_links']) || array_intersect(array_keys(is_array($input['declarations'] ?? null) ? $input['declarations'] : []), PrivateInvestmentPreparation::FIELDS)), 409, 'Esta preparação ainda não está disponível neste ambiente. As respostas foram preservadas.');
@@ -280,7 +340,14 @@ final class InvestorContext
$inputRules['declarations.'.$question['key']] = isset($question['options']) ? ['nullable', Rule::in(array_keys($question['options']))] : ['nullable', 'regex:/^\d+(?:\.\d+)?$/D', 'numeric', 'min:0', 'max:999999999999999999'];
}
return Validator::make($input, [...$inputRules, 'version' => 'required|integer|min:1', 'selection' => 'present|array:opportunity,sector,territory,park',
return Validator::make($input, [...$inputRules,
'opportunity_choices' => 'sometimes|array|max:100', 'opportunity_choices.*' => 'array:source,territory,location_reason,confirm_location_change,activities,corridor_key,portfolio_origins',
'opportunity_choices.*.portfolio_origins' => 'sometimes|array|max:100', 'opportunity_choices.*.portfolio_origins.*' => 'uuid',
'opportunity_choices.*.corridor_key' => ['sometimes', 'nullable', Rule::in(array_keys(config('investment_corridors')))],
'opportunity_choices.*.activities' => 'sometimes|array', 'opportunity_choices.*.activities.*' => 'string|distinct|in:production,processing,trade,transport,water,forestry,animal',
'opportunity_choices.*.source' => 'required|string|distinct|max:100', 'opportunity_choices.*.territory' => 'nullable|string|max:100',
'opportunity_choices.*.location_reason' => 'nullable|string|max:2000', 'opportunity_choices.*.confirm_location_change' => 'sometimes|boolean',
'version' => 'required|integer|min:1', 'selection' => 'present|array:opportunity,sector,territory,park',
'selection.*' => 'nullable|string|max:100', 'declarations' => 'nullable|array:project_title,project_summary,currency,expected_start_date,investor_origin,person_type,entity_stage,tax_residency_country,fiscal_residency_country,project_type,maturity_stage,'.implode(',', PrivateInvestmentPreparation::FIELDS), 'declarations.project_title' => 'nullable|string|max:200', 'declarations.project_summary' => 'nullable|string|max:5000',
'declarations.currency' => 'nullable|regex:/^[A-Z]{3}$/', 'declarations.expected_start_date' => 'nullable|date_format:Y-m-d', 'declarations.investor_origin' => 'nullable|in:national,foreign,mixed', 'declarations.person_type' => 'nullable|in:singular,collective,sole_trader,consortium', 'declarations.entity_stage' => 'nullable|in:existing,being_formed', 'declarations.tax_residency_country' => ['nullable', 'string', app(CountryCatalogue::class)->rule()], 'declarations.fiscal_residency_country' => ['nullable', 'string', app(CountryCatalogue::class)->rule()], 'declarations.project_type' => 'nullable|string|max:100', 'declarations.maturity_stage' => 'nullable|string|max:100',
'replace_fields' => 'nullable|array', 'replace_fields.*' => 'string|in:'.implode(',', self::FIELDS)])->validate();
@@ -303,6 +370,23 @@ final class InvestorContext
unset($links[$key]);
}
}
$choices = $old['opportunity_choices'] ?? [];
if (! array_key_exists('opportunity_choices', $data) && array_key_exists('territory', $data['selection'])) {
$source = $data['selection']['opportunity'] ?? ($old['selection']['opportunity']['key'] ?? null);
if ($source && str_starts_with($source, 'opportunity:')) {
$documented = Opportunity::findOrFail(substr($source, 12))->primary_territory_id;
abort_if($documented && $data['selection']['territory'] !== 'territory:'.$documented, 422,
'Confirme a alternativa territorial por ficha e indique o motivo; a localização documentada foi preservada.');
}
}
if (array_key_exists('opportunity_choices', $data)) {
$choices = app(InvestmentOpportunityChoices::class)->resolve($data['opportunity_choices'], $choices);
$data['selection']['opportunity'] = $choices[0]['opportunity']['key'] ?? null;
// Shared fields concern the first identified opportunity; other sites remain separate.
$data['selection']['territory'] = $choices[0]['territory']['key'] ?? null;
} elseif (array_key_exists('opportunity', $data['selection']) && $choices !== []) {
abort(422, 'Actualize a selecção identificada de oportunidades; não substitua silenciosamente o conjunto por uma ficha.');
}
$selection = $old['selection'];
foreach ($data['selection'] as $slot => $key) {
if ($key === null || $key === '') {
@@ -315,6 +399,16 @@ final class InvestorContext
abort_unless(in_array($ref['kind'], $expected, true), 422, 'Referência incompatível com a dimensão.');
$selection[$slot] = $ref;
}
$corridor = $choices[0]['corridor_key'] ?? (isset($selection['opportunity']) && $selection['opportunity']['kind'] === 'opportunity'
? app(InvestmentCorridorContext::class)->key(Opportunity::findOrFail($selection['opportunity']['id'])) : null);
if ($corridor && isset($selection['territory'])) abort_unless(app(InvestmentCorridorContext::class)->compatible($corridor, $selection['territory']['id']), 422, 'A localização não pertence ao corredor escolhido.');
if ($corridor && isset($selection['park'])) {
$eligible = array_column(app(InvestmentCorridorContext::class)->parks($corridor, $selection['territory']['id'] ?? null), 'id');
if (!in_array($selection['park']['id'], $eligible, true)) {
abort_if(!empty($data['selection']['park']), 422, 'A zona ou parque não tem enquadramento compatível documentado nesta localização.');
unset($selection['park']);
}
}
// A new opportunity may propose its explicitly stored sector; it never matches names.
if (isset($data['selection']['opportunity']) && ! array_key_exists('sector', $data['selection']) && isset($selection['opportunity']['fields']['sector_id'])) {
if (Sector::where('is_active', true)->whereKey($selection['opportunity']['fields']['sector_id'])->exists()) {
@@ -359,6 +453,16 @@ final class InvestorContext
unset($fields[$key],$provenance[$key]);
}
}
if ($corridor) {
foreach (['province_id','district_id','admin_post_id','locality_id','province','district','location'] as $key) {
if (isset($selection['territory']['fields'][$key])) {
$fields[$key]=(string)$selection['territory']['fields'][$key];
$provenance[$key]=Arr::except($selection['territory'],['fields']);
} else unset($fields[$key],$provenance[$key]);
}
$fields['special_zone_id']=$selection['park']['id']??'';
$fields['investment_regime']=$selection['park']['fields']['investment_regime']??'general';
}
$replaced = [];
foreach (array_unique([...array_keys($old['fields']), ...array_keys($fields)]) as $key) {
if (($old['fields'][$key] ?? null) !== ($fields[$key] ?? null)) {
@@ -371,10 +475,14 @@ final class InvestorContext
}
}
$changedFields = array_column($replaced, 'field');
$choicesChanged = $choices !== ($old['opportunity_choices'] ?? []);
$choiceInputsChanged = array_map(fn($c)=>Arr::except($c,['portfolio_origins']),$choices) !== array_map(fn($c)=>Arr::except($c,['portfolio_origins']),$old['opportunity_choices']??[]);
if ($choiceInputsChanged) $changed = array_values(array_unique([...$changed, 'opportunity', 'territory']));
elseif ($choicesChanged) $changed[] = 'portfolio_origins';
$conditionsChanged = (bool) array_intersect($changedFields, array_diff(self::FIELDS, ['project_title', 'project_summary']));
$dependencyChanged = $this->dependencyHash($old['selection']) !== $this->dependencyHash($selection) || $conditionsChanged;
$dependencyChanged = $choiceInputsChanged || $this->dependencyHash($old['selection']) !== $this->dependencyHash($selection) || $conditionsChanged;
$privateConditionsChanged = (bool) array_intersect($changedFields, PrivateInvestmentPreparation::FIELDS);
$calculationInputsChanged = $this->dependencyHash($old['selection']) !== $this->dependencyHash($selection) || (bool) array_intersect($changedFields, array_diff(self::FIELDS, ['project_title', 'project_summary', ...array_diff(PrivateInvestmentPreparation::FIELDS, ['total_investment', 'area_required', 'area_unit'])]));
$calculationInputsChanged = $choiceInputsChanged || $this->dependencyHash($old['selection']) !== $this->dependencyHash($selection) || (bool) array_intersect($changedFields, array_diff(self::FIELDS, ['project_title', 'project_summary', ...array_diff(PrivateInvestmentPreparation::FIELDS, ['total_investment', 'area_required', 'area_unit'])]));
$payload = $this->desk->decode($case->payload);
$results = $payload['investor_path']['results'] ?? [];
$requests = DB::table('desk_requests')->where('case_id', $id)->orderBy('id')->get(['id', 'status', 'protocol', 'version'])->all();
@@ -392,11 +500,12 @@ final class InvestorContext
$afterPreparation = app(PrivateInvestmentPreparation::class)->assess(['fields' => $fields, 'selection' => $selection, 'preparation_documents' => $links], $docs);
$privatePreparation = ['before' => $beforePreparation, 'after' => $afterPreparation];
}
$plan = ['case_id' => $id, 'version' => (int) $case->version, 'revision' => (int) $old['revision'], 'selection' => $selection, 'fields' => $fields, 'provenance' => $provenance,
$plan = ['opportunity_choices' => $choices, 'case_id' => $id, 'version' => (int) $case->version, 'revision' => (int) $old['revision'], 'selection' => $selection, 'fields' => $fields, 'provenance' => $provenance,
'preparation_documents' => $links, 'private_investment' => $privatePreparation,
'updated' => $replaced, 'maintained' => $kept, 'changed_dimensions' => $changed, 'dependent_results' => (int) $case->user_id === $actor->id && $calculationInputsChanged ? array_map(fn ($r) => ['id' => $r['id'], 'engine' => $r['engine']], $results) : [],
'requests' => $requests, 'review_steps' => $steps, 'dependency_changed' => $dependencyChanged, 'calculation_inputs_changed' => $calculationInputsChanged, 'documents_preserved' => DB::table('desk_documents')->where('case_id', $id)->count(),
'notice' => 'Não confirma incentivos, licenças, disponibilidade de terra, reserva ou autorização. Submissões e decisões anteriores mantêm o conteúdo enviado.'];
$plan['requirements_assessment'] = app(InvestmentContextRequirements::class)->assess(['opportunity_choices' => $choices, 'fields' => $fields]);
$hash = hash_hmac('sha256', json_encode(Arr::sortRecursive([$actor->id, $data, $plan]), JSON_THROW_ON_ERROR), config('app.key'));
return [...$plan, 'preview_hash' => $hash];
@@ -435,14 +544,16 @@ final class InvestorContext
foreach ($plan['review_steps'] as $step) {
$review[$step] = ['revision' => $revision, 'reason' => 'O contexto mudou; reveja os pressupostos e a evidência aplicável.'];
}
$context = ['preparation_documents' => $plan['preparation_documents'], 'preparation_snapshot' => $plan['private_investment']['after'] ?? $before['preparation_snapshot'] ?? null, 'revision' => $revision, 'selection' => $plan['selection'], 'fields' => $plan['fields'], 'provenance' => $plan['provenance'], 'review_steps' => $review,
'dependency_hash' => $this->dependencyHash(['selection' => $plan['selection'], 'fields' => Arr::except($plan['fields'], ['project_title', 'project_summary'])]), 'updated_at' => now()->toIso8601String(), 'updated_by' => $actor->id];
$context = ['opportunity_choices' => $plan['opportunity_choices'], 'preparation_documents' => $plan['preparation_documents'], 'preparation_snapshot' => $plan['private_investment']['after'] ?? $before['preparation_snapshot'] ?? null, 'revision' => $revision, 'selection' => $plan['selection'], 'fields' => $plan['fields'], 'provenance' => $plan['provenance'], 'review_steps' => $review,
'dependency_hash' => $this->dependencyHash([...($plan['opportunity_choices'] !== [] ? ['opportunity_choices' => $plan['opportunity_choices']] : []), 'selection' => $plan['selection'], 'fields' => Arr::except($plan['fields'], ['project_title', 'project_summary'])]), 'updated_at' => now()->toIso8601String(), 'updated_by' => $actor->id];
$context['requirements_assessment'] = $plan['requirements_assessment'];
if ($plan['calculation_inputs_changed']) {
foreach ($payload['investor_path']['results'] ?? [] as $i => $r) {
$payload['investor_path']['results'][$i] = [...$r, 'context_state' => 'review_required', 'context_invalidated_revision' => $revision, 'context_invalidated_at' => now()->toIso8601String()];
}
}
$payload['investor_path']['context'] = $context;
unset($payload['investor_path']['context_drafts'][(string)$actor->id]);
DB::table('desk_cases')->where('id', $id)->update(['payload' => $this->desk->encode($payload), 'title' => $context['fields']['project_title'] ?? $case->title, 'version' => $case->version + 1, 'updated_at' => now()]);
$this->desk->event($this->desk->case($actor, $id), $actor, 'investment_context_changed', ['before' => $before, 'after' => $context, 'affected_result_ids' => array_column($plan['dependent_results'], 'id'), 'preserved_request_ids' => array_column($plan['requests'], 'id')]);
$result = ['case_id' => $id, 'version' => (int) $case->version + 1, 'revision' => $revision, 'replayed' => false];
@@ -457,6 +568,7 @@ final class InvestorContext
$case = $this->desk->case($actor, $id);
abort_unless($this->desk->owns($actor, $case, false) || $actor->can('manage-portal'), 403);
$ctx = $this->state($case);
$ctx['land_preparation'] = $this->landPreparation($case);
$path = app(InvestorPath::class)->view($actor, $id);
$ctx['path_applicability'] = ['definition_id' => $path['definition_id'], 'definition_version' => $path['definition_version'], 'source_sha256' => $path['source_sha256'],
'steps' => array_values(array_map(fn ($step) => Arr::only($step, ['code', 'title', 'applicability', 'reason', 'state', 'context_review_required']), array_filter($path['steps'], fn ($step) => ! empty($step['branch']))))];
@@ -485,16 +597,42 @@ final class InvestorContext
return DB::table('desk_documents as l')->join('documents as d', 'd.id', '=', 'l.document_id')->where('l.case_id', $id)->whereNull('d.deleted_at')->get(['l.id', 'l.document_id', 'd.file_name', 'd.status', 'd.malware_scan_status', 'd.version_no', 'd.sha256'])->map(fn ($row) => (array) $row)->all();
}
public function landPreparation(\stdClass $case): array
{
$path = $this->desk->decode($case->payload)['investor_path'] ?? [];
$need = $path['facts']['land_needed'] ?? 'unknown';
return ['need' => in_array($need, ['yes', 'no'], true) ? $need : 'unknown', 'reason' => $path['fact_reasons']['land_needed'] ?? ''];
}
public function prefill(array $ctx): array
{
$f = $ctx['fields'];
$sim = Arr::except(Arr::only($f, self::FIELDS), ['sector', 'province', 'district', 'location', ...array_diff(PrivateInvestmentPreparation::FIELDS, ['total_investment', 'area_required', 'area_unit'])]);
// Entering this tool from an investment dossier selects the existing investment-analysis route.
// The reconciliation preserves any purpose deliberately chosen in an existing simulator draft.
$sim += ['journey_type' => 'investor', 'journey_type_label' => 'Avaliar o meu investimento', 'purpose' => 'simulate_only', 'purpose_label' => 'Apenas simular'];
if (($ctx['land_preparation']['need'] ?? null) === 'yes') {
$tenure = ['duat' => ['duat', 'DUAT'], 'lease' => ['leased', 'Arrendado']][$f['site_tenure'] ?? ''] ?? null;
if ($tenure) [$sim['land_status'], $sim['land_status_label']] = $tenure;
}
// Human labels travel with the exact existing identifiers, not inferred locations.
foreach (app(InvestmentGeography::class)->publicReferences()->whereIn('id', array_values(Arr::only($f, array_keys(InvestmentGeography::FIELDS))))->get(['id', 'name']) as $place) {
foreach (array_keys(InvestmentGeography::FIELDS) as $key) {
if (($f[$key] ?? null) === $place->id) $sim[$key.'_label'] = $place->name;
}
}
if (! empty($f['sector_id']) && ($sector = Sector::where('is_active', true)->find($f['sector_id']))) $sim['sector_id_label'] = $sector->name;
$zone = $ctx['selection']['park'] ?? null;
$sim['context_applicability_snapshot'] = json_encode([
'version' => 1, 'context_revision' => $ctx['revision'],
'zone' => $zone ? Arr::only($zone, ['id', 'title', 'href', 'version', 'sha256', 'fields', 'analysis']) : null,
'provenance' => $ctx['provenance'],
'opportunity_choices' => $ctx['opportunity_choices'] ?? [],
'requirements_assessment' => $ctx['requirements_assessment'] ?? null,
'path' => $ctx['path_applicability'] ?? null,
'land_preparation' => $ctx['land_preparation'] ?? null,
'field_provenance' => array_map(fn($origin) => Arr::only($origin, ['kind', 'title', 'version', 'context_revision']), $ctx['provenance'] ?? []),
'preparation' => isset($ctx['preparation_assessment']) ? Arr::except($ctx['preparation_assessment'], ['questions']) : null,
'fields' => Arr::only($f, ['investor_origin', 'tax_residency_country', 'entity_stage', 'currency', 'expected_start_date', 'investment_regime']),
'notice' => 'Referências e declarações para preparação; sem reconhecimento automático de elegibilidade ou benefício.',
+1 -1
View File
@@ -170,7 +170,7 @@ final class InvestorPath
'tools' => ['simulator' => (int) $case->user_id === $actor->id ? '/simulador-fiscal?context='.$id.($case->simulation_id ? '&draft='.$case->simulation_id : '') : null,
'desk' => '/balcao-virtual/dossiers/'.$id, 'guidance' => '/balcao-virtual/dossiers/'.$id.'/orientacao',
'library' => '/documentos?context='.$id, 'opportunities' => '/oportunidades?context='.$id, 'territory' => '/zonas?context='.$id, 'partners' => '/parcerias/painel?context='.$id,
'land' => (int) $case->user_id === $actor->id ? ($landCase ? '/investidor/terra-duat/'.$landCase->id : '/investidor/terra-duat/novo?context='.$id) : null]];
'land' => ($facts['land_needed'] ?? 'unknown') !== 'no' && (int) $case->user_id === $actor->id ? ($landCase ? '/investidor/terra-duat/'.$landCase->id : '/investidor/terra-duat/novo?context='.$id) : null]];
}
/** @param array<string,mixed> $input */
+3
View File
@@ -78,6 +78,9 @@ final class LegacyContent
$image = $this->publicResources()->where('r.id', $adaptation['image_resource_id'])->whereIn('d.media_type', ['image/png', 'image/jpeg', 'image/webp'])->first();
}
}
if ($slug === 'agronegocio') {
$localHtml = app(CorridorAgribusiness::class)->enrich($localHtml, app(CorridorAgribusinessOpportunities::class)->articleLinks());
}
preg_match_all('~href="([^"]+)"~i', $localHtml, $existingLinks);
$alreadyLinked = array_map(fn ($href) => html_entity_decode($href, ENT_QUOTES | ENT_HTML5, 'UTF-8'), $existingLinks[1]);
$relatedArticles = [];
@@ -0,0 +1,55 @@
<?php
namespace App\Services;
use App\Models\Opportunity;
/** Read-only grouping of the existing public catalogue. */
final class OpportunityCorridorExplorer
{
public function groups(array $filters): array
{
$catalogue = app(InvestmentCatalogue::class);
$groups = [];
foreach ($catalogue->query($filters)->get() as $record) {
$context = $catalogue->context($record->id);
$classification = $context['classification'] ?? [];
$corridors = collect($context['corridors'])->mapWithKeys(fn ($c) => ['corridor:'.$c->id => $c->name])->all();
foreach ($classification as $relation) {
if ($relation['target_type'] === 'corridor' && ! in_array($relation['purpose'], ['proximity', 'administrative_boundary'], true)) {
$corridors['corridor:'.$relation['target_id']] = $relation['label'];
}
}
// The approved source names are not aliases for similarly named master records.
$source = data_get($record->workflow_context, 'corridor_agribusiness');
$chain = null;
if (is_array($source) && ($source['source_sha256'] ?? null) === CorridorAgribusinessOpportunities::SOURCE_HASH) {
$corridors['source:'.hash('sha256', $source['corridor'])] = $source['corridor'];
$chain = $source['chain'];
}
$chains = collect($classification)->where('target_type', 'chain')->pluck('label')->all();
if ($chain) $chains[] = $chain;
$stages = collect($classification)->pluck('production_stage')->filter()->unique()->values()->all();
// Keep the source's actual activity description rather than inventing stage labels.
if ($stages === [] && $chain && ! empty($source['summary'])) $stages = [$source['summary']];
$path = collect($context['primary_territory_path'] ?? []);
$province = $path->firstWhere('location_type', 'province')->name ?? $record->province;
$district = $path->firstWhere('location_type', 'district')->name ?? $record->district;
$item = ['id' => $record->id, 'title' => $record->title, 'slug' => $record->slug, 'summary' => $record->summary,
'province' => $province, 'district' => $district, 'chains' => array_values(array_unique($chains)),
'stages' => $stages, 'maturity_label' => $record->maturity_label];
foreach ($corridors ?: ['other' => 'Outras localizações'] as $key => $name) {
$groups[$key] ??= ['id' => $key, 'name' => $name, 'opportunities' => []];
$groups[$key]['opportunities'][$record->id] = $item;
}
}
foreach ($groups as &$group) {
$group['opportunities'] = array_values($group['opportunities']);
$group['count'] = count($group['opportunities']);
$group['locations'] = array_values(array_unique(array_filter(array_map(fn ($r) => implode(' / ', array_filter([$r['province'], $r['district']])), $group['opportunities']))));
}
unset($group);
uasort($groups, fn ($a, $b) => ($a['id'] === 'other' ? 1 : 0) <=> ($b['id'] === 'other' ? 1 : 0) ?: strnatcasecmp($a['name'], $b['name']));
return array_values($groups);
}
}
+3
View File
@@ -27,6 +27,9 @@ class OpportunityManagement
public function values(Opportunity $record): array
{
$base = $record->only(self::FIELDS);
if (data_get($record->workflow_context, 'publication.purpose') === OpportunityPromotion::PURPOSE) {
$base = [...$base, ...($record->workflow_context['promotional_private_base'] ?? [])];
}
$draft = $record->management_draft ? json_decode($record->management_draft, true, 512, JSON_THROW_ON_ERROR) : [];
return [...$base, ...($this->closed($record) ? $draft : array_intersect_key($draft, array_flip(self::PRIVATE_DRAFT_FIELDS)))];
+168
View File
@@ -0,0 +1,168 @@
<?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;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
/** Tempo's promotional disclosure, independent of investment execution approval. */
final class OpportunityPromotion
{
public const PURPOSE = 'promotional_preparation';
public const NOTICE = 'Em preparação. A divulgação não confirma financiamento, disponibilidade de terreno, licenças ou autorização de execução do investimento.';
public function allowed(User $actor, Opportunity $record): bool
{
$access = app(PortalProfileAccess::class);
if ($record->trashed() || $record->owner_user_id === null || ! $access->canView($actor, $record)
|| $access->has($actor, 'auditor') || in_array($record->status, ['archived', 'rejected'], true)) return false;
// Never overwrite a normal approved publication or its workflow decisions.
if ($record->published_at && data_get($record->workflow_context, 'publication.purpose') !== self::PURPOSE) return false;
$internal = $access->internalAssignment($actor, $record->opportunity_category);
return ($internal && $access->promoterRecordScope($actor, $record) && $access->operational($actor, 'edit', $record->id))
|| ($access->scoped($actor, 'publisher', $record->organization_id, $record->opportunity_category)
&& $access->operational($actor, 'publish', $record->id));
}
private function authorize(User $actor, Opportunity $record): void
{
abort_unless($this->allowed($actor, $record), 403, 'A designação e o âmbito existentes não autorizam esta divulgação.');
if (! app(PortalProfileAccess::class)->mfa($actor)) {
throw new \Illuminate\Http\Exceptions\HttpResponseException(response()->json(['message' => 'Confirme a entrada normal com MFA recente para divulgar.'], 403)->header('X-Portal-Reauthentication', 'required'));
}
}
public function editor(User $actor, Opportunity $record): array
{
abort_unless($this->allowed($actor, $record), 403);
$saved = data_get($record->workflow_context, 'promotional_summary', []);
// Only previously public text is recovered automatically. No private contact/document is copied.
$existing = data_get($record->workflow_context, 'publication.purpose') === self::PURPOSE;
$form = $saved['input'] ?? ['title' => $existing ? $record->title : '', 'summary' => $existing ? $record->summary : '',
'description' => $existing ? $record->description : '', 'source_reference' => '', 'edition' => '', 'source_date' => '',
'limitations' => '', 'territory_ids' => $existing ? DB::table('opportunity_locations')->where('opportunity_id', $record->id)->pluck('geo_location_id')->all() : [],
'sector_id' => $record->sector_id];
return ['record' => ['id' => $record->id, 'reference' => $record->reference], 'etag' => app(OpportunityManagement::class)->etag($record),
'initial' => $form, 'publicNow' => $existing && $record->status === 'published',
'reauthenticationRequired' => ! app(PortalProfileAccess::class)->mfa($actor),
'sectors' => \App\Models\Sector::catalog()->orderBy('name')->get(['id', 'name']),
'territories' => app(InvestmentGeography::class)->publicReferences()->orderBy('name')->get(['id', 'name', 'location_type']),
'history' => DB::table('audit_events')->where('source_system', 'tempo-preparation-publication')
->where('after_data->record_id', $record->id)->orderByDesc('occurred_at')->get(['id', 'action', 'actor_user_id', 'occurred_at'])];
}
public function preview(User $actor, Opportunity $record, array $input): array
{
$this->authorize($actor, $record);
$data = Validator::make($input, ['etag' => 'required|string|size:64', 'action' => 'required|in:disclose,withdraw',
'title' => 'required_if:action,disclose|nullable|string|max:255', 'summary' => 'required_if:action,disclose|nullable|string|max:499',
'description' => 'nullable|string|max:6000', 'limitations' => 'nullable|string|max:2500',
'source_reference' => 'nullable|string|max:2000', 'edition' => 'nullable|string|max:150', 'source_date' => 'nullable|date_format:Y-m-d',
'sector_id' => ['nullable', 'integer', Rule::in(\App\Models\Sector::catalog()->pluck('id')->all())],
'territory_ids' => 'sometimes|array|max:30', 'territory_ids.*' => 'required|uuid|distinct',
])->validate();
abort_unless(hash_equals(app(OpportunityManagement::class)->etag($record), $data['etag']), 409, 'A ficha mudou. Reabra antes de preparar outra prévia.');
if ($data['action'] === 'withdraw') abort_unless(data_get($record->workflow_context, 'publication.purpose') === self::PURPOSE, 409);
$paths = [];
foreach ($data['territory_ids'] ?? [] as $id) {
$path = app(InvestmentGeography::class)->path($id, true);
abort_unless(count($path) > 0 && collect($path)->contains('id', $id), 422, 'Seleccione apenas territórios públicos existentes.');
$paths[] = $path;
}
$text = fn ($key) => trim(strip_tags($data[$key] ?? ''));
$body = implode("\n\n", array_filter([$text('description'), self::NOTICE,
'Limitações e lacunas: '.($text('limitations') ?: 'Informação ainda por completar.'),
'Fonte: '.($text('source_reference') ?: 'Não indicada.'), 'Edição: '.($text('edition') ?: 'Não indicada.'),
'Data-base: '.($data['source_date'] ?? 'Não indicada.')]));
$body = OpportunityPublicText::description($record, $body);
$projection = ['title' => $text('title'), 'summary' => $text('summary'), 'description' => $body,
'sector_id' => $data['sector_id'] ?? null, 'maturity_label' => 'Em preparação', 'notice' => self::NOTICE,
'territories' => array_map(fn ($p) => ['id' => end($p)->id, 'label' => implode(' / ', array_column($p, 'name'))], $paths)];
if ($data['action'] === 'disclose') abort_unless($projection['title'] !== '' && $projection['summary'] !== '', 422, 'Indique título e resumo em texto simples.');
if ($data['action'] === 'withdraw') $projection = [...$projection, ...$record->only(['title', 'summary', 'description', 'sector_id', 'maturity_label'])];
$payload = ['actor_id' => $actor->id, 'record_id' => $record->id, 'etag' => $data['etag'], 'action' => $data['action'],
'input' => $data, 'projection' => $projection, 'expires' => now()->addMinutes(15)->timestamp];
return ['action' => $data['action'], 'projection' => $projection, 'token' => Crypt::encryptString(json_encode($payload, JSON_THROW_ON_ERROR)),
'current' => $record->only(['title', 'summary', 'description', 'status']), 'writes' => false];
}
public function confirm(User $actor, int $id, array $input): array
{
$data = Validator::make($input, ['token' => 'required|string|max:300000', 'operation_key' => 'required|uuid',
'confirm' => 'required|accepted', 'reason' => 'required|string|min:10|max:1000'])->validate();
try { $preview = json_decode(Crypt::decryptString($data['token']), true, flags: JSON_THROW_ON_ERROR); }
catch (\Throwable) { abort(422, 'Prévia inválida. Prepare outra prévia.'); }
abort_unless(($preview['actor_id'] ?? null) === $actor->id && ($preview['record_id'] ?? null) === $id, 403);
return DB::transaction(function () use ($actor, $id, $data, $preview) {
DB::select('SELECT pg_advisory_xact_lock(hashtext(?))', ['promotional-operation:'.$data['operation_key']]);
$record = Opportunity::whereKey($id)->lockForUpdate()->firstOrFail();
$this->authorize($actor, $record);
$digest = hash('sha256', $data['token'].'|'.$data['reason']);
$replay = DB::table('audit_events')->where('source_system', 'tempo-preparation-publication')
->where('after_data->operation_key', $data['operation_key'])->first();
if ($replay) {
$prior = json_decode($replay->after_data, true);
abort_unless($prior['record_id'] === $id && (int) $replay->actor_user_id === $actor->id && hash_equals($prior['request_hash'], $digest), 409, 'Chave usada noutra operação.');
return ['id' => $id, 'replayed' => true, 'message' => 'Operação já registada; nenhuma alteração repetida.'];
}
abort_unless(($preview['expires'] ?? 0) >= now()->timestamp, 409, 'A prévia expirou. Prepare outra prévia.');
abort_unless(hash_equals(app(OpportunityManagement::class)->etag($record), $preview['etag']), 409, 'Alteração concorrente: a ficha foi preservada. Reabra e reveja a nova versão.');
$before = $record->getRawOriginal();
$oldLocations = DB::table('opportunity_locations')->where('opportunity_id', $id)->pluck('geo_location_id')->all();
$context = $record->workflow_context ?? [];
if ($preview['action'] === 'withdraw') {
$record->status = 'suspended';
$record->published_at = null;
$context['publication']['visibility'] = 'withdrawn';
$action = 'promotional_summary_withdrawn';
} else {
$p = $preview['projection'];
if ($p['sector_id'] !== null) abort_unless(\App\Models\Sector::catalog()->whereKey($p['sector_id'])->exists(), 409, 'O sector deixou de estar disponível no catálogo. Prepare outra prévia.');
$paths = [];
foreach ($preview['input']['territory_ids'] ?? [] as $territory) {
$path = app(InvestmentGeography::class)->path($territory, true);
abort_unless(count($path) && collect($path)->contains('id', $territory), 409, 'Um território deixou de estar disponível. Prepare outra prévia.');
$paths[] = $path;
}
$currentTerritories = array_map(fn ($path) => ['id' => end($path)->id, 'label' => implode(' / ', array_column($path, 'name'))], $paths);
abort_unless($currentTerritories === $p['territories'], 409, 'O enquadramento territorial mudou. Prepare outra prévia.');
// Preserve the original private draft bytes; retain its former base
// columns in the existing hidden context before projecting public text.
$context['promotional_private_base'] ??= $record->only(OpportunityManagement::FIELDS);
$context['working_state'] ??= app(PortalProfileAccess::class)->state($record);
$context['promotional_summary'] = ['input' => $preview['input'], 'projection' => $p];
$context['publication'] = ['visibility' => 'public', 'purpose' => self::PURPOSE, 'actor_id' => $actor->id,
'at' => now()->toIso8601String(), 'execution_approved' => false, 'private_draft_preserved' => true];
$action = data_get($before, 'status') === 'published' ? 'promotional_summary_updated' : 'promotional_summary_published';
foreach (['title', 'summary', 'description', 'sector_id', 'maturity_label'] as $field) $record->{$field} = $p[$field];
foreach (['promoter_name', 'promoter_type', 'financing_modality', 'expected_impact', 'maturity_level', 'estimated_investment_amount', 'estimated_investment_currency'] as $field) $record->{$field} = null;
$main = $paths[0] ?? [];
$record->primary_territory_id = $main ? end($main)->id : null;
$record->province = collect($main)->firstWhere('location_type', 'province')?->name;
$record->district = collect($main)->firstWhere('location_type', 'district')?->name;
$record->geography_scope = count($paths) > 1 ? 'multiple' : ($main ? 'specific' : 'unspecified');
DB::table('opportunity_locations')->where('opportunity_id', $id)->delete();
foreach ($preview['input']['territory_ids'] ?? [] as $territory) DB::table('opportunity_locations')->insert(['opportunity_id' => $id, 'geo_location_id' => $territory]);
$record->status = 'published';
$record->is_featured = false;
$record->last_validated_at = null;
$record->published_at = now();
}
$record->workflow_context = $context;
$record->management_version++;
$record->save();
DB::table('audit_events')->insert(['source_system' => 'tempo-preparation-publication', 'entity_type' => 'opportunity',
'actor_user_id' => $actor->id, 'action' => $action, 'data_classification' => 'restricted', 'correlation_id' => (string) Str::uuid(),
'before_data' => json_encode(['record_id' => $id, 'record' => $before, 'territories' => $oldLocations], JSON_THROW_ON_ERROR),
'after_data' => json_encode(['record_id' => $id, 'operation_key' => $data['operation_key'], 'request_hash' => $digest,
'reason' => $data['reason'], 'projection' => $preview['projection'], 'execution_approved' => false,
'record' => $record->getRawOriginal()], JSON_THROW_ON_ERROR)]);
return ['id' => $id, 'replayed' => false, 'message' => $preview['action'] === 'withdraw' ? 'Resumo retirado; cadastro e histórico preservados.' : 'Resumo divulgado como Em preparação. Não autoriza execução do investimento.'];
});
}
}
+32
View File
@@ -0,0 +1,32 @@
<?php
namespace App\Services;
use App\Models\Opportunity;
/** Presentation only: originals, editorial fields and audit records remain intact. */
final class OpportunityPublicText
{
public static function description(Opportunity $record, string $text): string
{
$source = data_get($record->workflow_context, 'corridor_agribusiness');
if (! is_array($source) || ($source['source_sha256'] ?? null) !== CorridorAgribusinessOpportunities::SOURCE_HASH) return $text;
$hash = CorridorAgribusinessOpportunities::SOURCE_HASH;
$sourceLine = 'Fonte: '.$source['source'].'; SHA-256 '.$hash;
$paragraphs = explode("\n\n", $text);
$result = [];
foreach ($paragraphs as $paragraph) {
$paragraph = trim($paragraph);
if (in_array($paragraph, [$sourceLine, $sourceLine.'.', 'Edição: Não indicada.', 'Data-base: Não indicada.',
'Limitações e lacunas: '.CorridorAgribusinessOpportunities::LIMITATIONS,
OpportunityPromotion::NOTICE], true)) continue;
if ($paragraph === 'Enquadramento documental: '.$source['corridor'].'.') $paragraph = $source['corridor'].'.';
if ($paragraph === CorridorAgribusinessOpportunities::LIMITATIONS) {
$paragraph = 'A localização exacta, a disponibilidade de terreno e as condições do investimento serão definidas durante a preparação do projecto.';
}
if ($paragraph !== '') $result[] = $paragraph;
}
return implode("\n\n", $result);
}
}
+32 -1
View File
@@ -60,9 +60,11 @@ final class SimulationFormDrafts
throw new HttpException(409, 'Já existe um rascunho activo. Carregue-o antes de guardar novas alterações.');
}
if ($case) $data['economic_snapshot'] = $this->landContext($data['economic_snapshot'], $case);
$snapshotHash = hash('sha256', json_encode($this->canonical($data), JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE));
$draft = SimulationFormDraft::query()->create([
'id' => (string) Str::uuid(), 'owner_user_id' => $actorId, 'anonymous_token_hash' => $tokenHash,
'version_no' => 1, 'create_key_hash' => $keyHash, 'create_payload_hash' => $payloadHash, 'snapshot_sha256' => $payloadHash,
'version_no' => 1, 'create_key_hash' => $keyHash, 'create_payload_hash' => $payloadHash, 'snapshot_sha256' => $snapshotHash,
'personal_snapshot' => SimulationFormDraft::encryptSnapshot($data['personal_snapshot']),
'economic_snapshot' => SimulationFormDraft::encryptSnapshot($data['economic_snapshot']),
'expires_at' => $actorId === null ? now()->addDays(30) : null,
@@ -89,6 +91,7 @@ final class SimulationFormDrafts
$this->checkContext($request, $draft);
$this->requireMatch($request, $draft);
$previousEconomic = SimulationFormDraft::decryptSnapshot($draft->economic_snapshot);
$data['economic_snapshot'] = $this->preserveInactiveLandStatus($draft, $data['economic_snapshot'], $previousEconomic, $actorId);
if (($data['economic_snapshot']['reuse_simulation'] ?? null) === 'no') {
unset($data['economic_snapshot']['simulation_sources']);
} elseif (is_array($previousEconomic['simulation_sources'] ?? null)) {
@@ -456,6 +459,34 @@ final class SimulationFormDrafts
return $query;
}
private function preserveInactiveLandStatus(SimulationFormDraft $draft, array $economic, array $previous, ?int $actorId): array
{
if (! $actorId || ! config('investor-path.enabled') || ! app(VirtualDesk::class)->installed()) return $economic;
// Use the real owner-bound dossier even if a request omits its context parameter.
$case = DB::table('desk_cases')->where('simulation_id', $draft->id)->where('user_id', $actorId)->first();
if (! $case) return $economic;
return $this->landContext($economic, $case, $previous);
}
private function landContext(array $economic, \stdClass $case, ?array $previous = null): array
{
$decision = app(InvestorContext::class)->landPreparation($case);
if ($decision['need'] !== 'yes' && $previous !== null) {
if (array_key_exists('land_status', $previous)) $economic['land_status'] = $previous['land_status'];
else unset($economic['land_status']);
if (isset($previous['selection_labels']['land_status'])) $economic['selection_labels']['land_status'] = $previous['selection_labels']['land_status'];
else unset($economic['selection_labels']['land_status']);
}
$snapshot = json_decode($economic['context_applicability_snapshot'] ?? $previous['context_applicability_snapshot'] ?? '', true);
if (! is_array($snapshot) || ($snapshot['version'] ?? null) !== 1) {
$snapshot = ['version' => 1, 'context_revision' => app(InvestorContext::class)->state($case)['revision']];
}
$snapshot['land_preparation'] = $decision;
$economic['context_applicability_snapshot'] = json_encode($snapshot, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
return $economic;
}
private function checkContext(Request $request, SimulationFormDraft $draft): void
{
$context = $request->input('context');
+13
View File
@@ -0,0 +1,13 @@
<?php
// Functional configuration expressly defined by Tempo, 23/10/2026.
// Codes identify the existing national territorial registry; no geometry is inferred.
return [
'pemba-lichinga' => ['name' => 'Corredor de Pemba–Lichinga', 'provinces' => ['02', '01']],
'nacala' => ['name' => 'Corredor de Nacala', 'provinces' => ['03']],
'vale-zambeze' => ['name' => 'Corredor do Vale do Zambeze', 'provinces' => ['05', '04']],
'beira' => ['name' => 'Corredor da Beira', 'provinces' => ['07', '06']],
'limpopo' => ['name' => 'Corredor do Limpopo', 'provinces' => ['09']],
// Tempo's final clarification: both Maputo Province and Maputo City.
'maputo' => ['name' => 'Corredor de Maputo', 'provinces' => ['10', '11']],
];
@@ -0,0 +1,181 @@
{
"source": "investimentos nos corredores.docx",
"source_sha256": "1473b06c48aa234e771660e3f2a51b9d2e4e1943bd52d878e0ae854ee6657055",
"introduction": "Os corredores de desenvolvimento integram cadeias de valor prioritárias que podem ser apresentadas no portal InvesteMoz como oportunidades concretas de investimento na produção, transformação, logística e comercialização. Para cada corredor, são destacadas abaixo as oportunidades associadas aos produtos com maior potencial.",
"segments": [
"Produção primária e fornecimento de insumos",
"Processamento e transformação agro-industrial",
"Armazenagem, cadeia de frio e logística",
"Embalagem, distribuição, comercialização e exportação",
"Infraestruturas e serviços de apoio às cadeias de valor"
],
"groups": [
{
"name": "Corredor de Pemba–Lichinga",
"opportunities": [
{
"chain": "Batata, trigo, feijão, milho e soja",
"description": "Produção comercial, armazenamento e processamento de batata, trigo, feijão, milho e soja."
},
{
"chain": "Algodão",
"description": "Descaroçamento, fiação e produção de óleo e derivados de algodão."
},
{
"chain": "Sésamo",
"description": "Limpeza, prensagem, embalagem e exportação de sésamo."
},
{
"chain": "Florestas",
"description": "Viveiros, plantações comerciais, madeira processada e produtos florestais sustentáveis."
},
{
"chain": "Avicultura",
"description": "Produção de rações, incubação, criação, abate e cadeia de frio para avicultura."
}
]
},
{
"name": "Corredor de Nacala",
"opportunities": [
{
"chain": "Mandioca",
"description": "Produção e transformação de mandioca em farinha, amido e outros derivados."
},
{
"chain": "Milho, amendoim e sésamo",
"description": "Produção, armazenagem e processamento de milho, amendoim e sésamo."
},
{
"chain": "Algodão",
"description": "Descaroçamento e transformação de algodão."
},
{
"chain": "Frutas e caju",
"description": "Pomares, processamento, embalagem e exportação de frutas e caju."
},
{
"chain": "Florestas",
"description": "Silvicultura comercial, madeira processada e produtos florestais."
},
{
"chain": "Avicultura",
"description": "Produção de rações, criação, abate e distribuição de produtos avícolas."
}
]
},
{
"name": "Corredor do Vale do Zambeze",
"opportunities": [
{
"chain": "Arroz",
"description": "Produção irrigada, moagem, embalagem e comercialização de arroz."
},
{
"chain": "Milho e batata",
"description": "Produção, secagem, armazenagem e transformação de milho e batata."
},
{
"chain": "Sésamo",
"description": "Limpeza, prensagem e exportação de sésamo."
},
{
"chain": "Algodão",
"description": "Descaroçamento e transformação de algodão."
},
{
"chain": "Avicultura",
"description": "Produção de rações, incubação, criação e abate de aves."
},
{
"chain": "Bovinos e caprinos",
"description": "Criação, engorda, abate, processamento de carne e cadeia de frio para bovinos e caprinos."
}
]
},
{
"name": "Corredor da Beira",
"opportunities": [
{
"chain": "Milho, trigo e arroz",
"description": "Produção, silos, moagem e processamento de milho, trigo e arroz."
},
{
"chain": "Soja",
"description": "Esmagamento de soja, produção de óleo vegetal e fabrico de rações."
},
{
"chain": "Hortícolas",
"description": "Produção irrigada, conservação, embalagem e distribuição de hortícolas."
},
{
"chain": "Sésamo",
"description": "Limpeza, prensagem e exportação de sésamo."
},
{
"chain": "Açúcar",
"description": "Produção e refinação de açúcar e aproveitamento de subprodutos."
},
{
"chain": "Florestas",
"description": "Plantações comerciais, processamento de madeira e produtos florestais."
},
{
"chain": "Carne bovina",
"description": "Criação, engorda, abate, processamento e cadeia de frio para carne bovina."
}
]
},
{
"name": "Corredor do Limpopo",
"opportunities": [
{
"chain": "Arroz",
"description": "Produção irrigada, moagem, embalagem e comercialização de arroz."
},
{
"chain": "Hortícolas",
"description": "Produção em estufas e campo aberto, conservação, embalagem e distribuição de hortícolas."
},
{
"chain": "Carne vermelha",
"description": "Criação, engorda, abate, processamento e cadeia de frio para carne vermelha."
},
{
"chain": "Avicultura",
"description": "Produção de rações, incubação, criação, abate e distribuição de produtos avícolas."
},
{
"chain": "Infraestruturas e serviços de apoio",
"description": "Infraestruturas de irrigação, armazenagem, transporte refrigerado e centros de comercialização."
}
]
},
{
"name": "Corredor de Maputo",
"opportunities": [
{
"chain": "Arroz",
"description": "Produção irrigada, moagem, embalagem e fornecimento de arroz aos mercados urbanos e regionais."
},
{
"chain": "Hortícolas",
"description": "Produção, conservação, embalagem, transformação e distribuição de hortícolas."
},
{
"chain": "Carne vermelha",
"description": "Criação, engorda, abate, processamento, distribuição e exportação regional de carne vermelha."
},
{
"chain": "Avicultura",
"description": "Produção de rações, incubação, criação, abate e comercialização de produtos avícolas."
},
{
"chain": "Infraestruturas e serviços de apoio",
"description": "Plataformas logísticas, centros de distribuição, cadeia de frio e serviços de exportação."
}
]
}
],
"sector": "Agronegócio"
}
@@ -4,6 +4,10 @@ Data: 2026-10-03. Âmbito: prompt master recebido em `Pasted text.txt`, document
## Estado inicial e ambiente
**Orientação de publicação e percurso do lote, 23/10/2026:** as 34 oportunidades de Agronegócio ficam publicáveis sem relações obrigatórias com corredores/clusters/projectos. Lacuna encontrada: a comparação múltipla existente não era transportada ao dossier. Correcção aditiva no contexto cifrado existente, com escolhas identificadas e localização por ficha, confirmação de alternativa, requisitos/fontes/documentos/entidades por contexto, reutilização no simulador e conservação de resultados/histórico. Sete testes do contexto (132 asserções) e um do lote pela sessão HTTP (15) passaram. Publicação QA efectuada; instalação no destino separa código, cadastro privado e divulgação autenticada. Ver [orientação e guia no manual](unification/traceability-and-manual.md). Não altera Turismo de Inhambane nem concede benefícios por localização. Lacunas de modalidade/dimensão/acto concreto permanecem explícitas, sem nova revalidação de fontes oficiais aceites.
**Correcção de âmbito, 23/10/2026 — agronegócio e percurso existente:** a lista informativa instalada não encerrava o cadastro. Preparadas e efectivamente cadastradas 34 oportunidades privadas em QA, com as seis designações documentais intactas, sector, fonte/hash e lacunas explícitas. Reutilizada a candidata de divulgação promocional `9d3bfa89` no fluxo necessário, sem alterações independentes de administração de contas nem novos papéis. Equipa edita/recupera e divulga/actualiza/retira pela interface; público acede à ficha individual, manifestação e roteiro com referência confirmada no mesmo dossier. Três testes específicos, 553 asserções; tipos/build concluídos; tabelas/linhas preexistentes comparadas e preservadas. As divulgações dos testes foram revertidas; as 34 fichas permanecem privadas. Sem alteração da homologação nesta correcção. Carregamento inicial técnico QA já executado; não é dependência das operações correntes. Guia, limites e recibo na [secção de correcção do manual](unification/traceability-and-manual.md#correcção-de-âmbito--34-oportunidades-cadastradas-e-roteiro-em-qa-23102026). Não encerra viabilidade, implantação, financiamento ou execução das oportunidades.
| Item | Evidência observada |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Branch / commit | `main` / `4f35313d06ccd597a4ebf9ed043964d898c95813` |
@@ -334,8 +338,18 @@ Proposta de entrega da candidata `1bcf34c0`: ver «Proposta de instalação comp
Nos requisitos existentes de gestão/publicação `MASTER-9f6dd1d9-0001`, `MASTER-9f6dd1d9-0003` e `MASTER-02f44baf-0002`, registar o recorte solicitado pela Tempo: procedimentos com regime/edição/fontes, lotes, rascunhos recuperáveis, prévia, actos versionados, catálogo/ficha/anexos anónimos, filtros, impressão e acompanhamento por conta simples. Evidência: secção «Concursos e anúncios — implementação em QA» do manual existente e `tests/Feature/ProcurementTest.php`, com passagem adicional no navegador dos três regimes, prorrogação, cancelamento, download e acompanhamento. Reutilizadas autorizações e biblioteca; não foram criados papéis ou aprovadores. Origem funcional: pedido expresso da Tempo; flag, prévia assinada e controlo de versão são escolhas técnicas. Entrega delimitada a QA: integração em main, migração/activação no destino e anúncios reais seguem o processo existente. Não fecha requisitos integrais de contratação/recepção de propostas, nem declara parametrizado todo o articulado legal. Geometrias permanecem no recorte separado já entregue.
### Preservação do concluído e reposição — 23/10/2026
Origem: pedido expresso da Tempo de inventariar o concluído e repor funcionalidades desaparecidas antes de outra ronda de testes/validações. Inventariados os mesmos 43 componentes do manual e os 17 percursos da matriz executável existente; os componentes de interface referenciados estão presentes. Estados históricos foram reconciliados com entregas posteriores, sem revogar aceitações por falta de nova prova. Não se criou nova matriz/backlog nem se promoveu uma funcionalidade parcial a concluída.
Perda confirmada: carteira e projectos âncora. Os 29 dossiers existiam em homologação, todos em rascunho, apesar de os exemplares locais idênticos estarem publicados com aprovação documental do proprietário de 26/09. Conteúdo, fonte e título conferidos por identidade/código e hashes nos 29; o commit `e65cb269` de 06/10 retirou o caminho de publicação com essa aprovação do importador. Isso explica a diferença do processo de instalação; não prova quando a visibilidade desapareceu de um endereço específico. Reposta a visibilidade dos 15 projectos, 8 clusters e 6 corredores em QA e homologação, com versões e 29 eventos de reposição; sem importação, reescrita de conteúdos, alteração dos mestres ou novas aprovações/papéis.
Backup/restauro fresco de 174 tabelas antes da escrita activa; depois da reposição, 171 tabelas inalteradas. Diferenças limitadas a dossiers, auditoria e sessões. Biblioteca, 20 artigos públicos, 28 privados, geometrias e permissões preservados. Conferência pública delimitada: carteira 29, projectos 15, clusters 8, corredores 6. Sem suite geral de testes nesta passagem. Evidência, limites e regra de preservação em `unification/preservation-inventory-20261023.json`, secção «Preservação do concluído — inventário e reposição» do manual e instrução permanente de 23/10 em `AGENTS.md`.
### Acervo legado — correspondência e preparação QA, 22/10/2026
Fecho do delta privado na homologação: candidata `fb4e7d89` integrada em `65eddf6b`; backup/restauro isolado, transporte privado e cadastro autenticado dos 28 concluídos, sem publicação. Pesquisa/editor/prévia/recuperação conferidos para os 28; gravação/recuperação visual da revisão 3 pela conta real no recurso 32. Originais, registos anteriores e permissões preservados. Recibo `unification/legacy-editorial-installation-receipt-20261022.json` e secção «Legado — cadastro privado concluído na homologação» do manual. Publicação e revisão editorial efectiva cabem à equipa pela interface; dois originais continuam incompletos. Não fecha nem diagnostica a falha separada da carteira/projectos âncora, nem constitui aceitação do commit/pacote pela Tempo.
Actualização do mesmo requisito: os 28 rascunhos têm edição, leitura privada, prévia sem guardar, recuperação e publicação/retirada pela interface em QA, preservando originais, origem e revisões. Seis testes/1.000 asserções, TypeScript e build passaram; publicação positiva apenas em transacções revertidas. Pacote privado v2 preparado com 28 originais e proveniência; não transportado nem publicado na homologação. Guia, permissões existentes, lacunas de imagens, dependências da entrega e reversão na secção «Legado — edição e leitura dos 28 rascunhos em QA» do manual. Pedido funcional expresso da Tempo; hash de âmbito, prévia e controlo de versão são escolhas técnicas, sem novo papel/aprovador. Conserva o saldo de 20 públicos aceites e dois originais incompletos, sem repetir provas ou fechar a integração integral.
Referência do acervo legado, 22/10/2026: a lista única `v2/auditoria-portal/legado-correspondencia-integral.md` e o CSV existente reconciliam 50 identidades (20 publicados, 28 por integrar no destino, um vazio, um sem título). 20/20 páginas conferidas por HTTP anónimo/identidade/origem; cartões de Turismo observados no destino. Os 28 restantes avançaram para rascunhos internos QA, com ClamAV real e repetição idempotente, sem publicação. Recibo e limites no manual, secção «Legado — correspondência integral e avanço privado QA». As duas lacunas editoriais foram confirmadas no original. Não fecha a integração integral nem substitui revisão editorial por prova HTTP; nenhuma alteração activa nesta passagem.
@@ -0,0 +1,32 @@
{
"target": "https://teste-investidor.tempo.co.mz",
"authorization": "Tempo: rever e disponibilizar a entrega consolidada pelo processo Git existente; 23/10/2026",
"base": "e1f010f941e9993645e131b34ec1f4c3fe82c71b",
"continuity": "3d877ec7",
"qa_recovery_record": "115c7731",
"served_commit": "7c1f532134e4492dec010d0c511f669a1e2c23a7",
"finished_at": "2026-10-23T13:16:11.213670+00:00",
"status": "installed",
"release": "/var/www/investmoz-homologacao/releases/7c1f532134e4492dec010d0c511f669a1e2c23a7-524bd081",
"previous_release": "/var/www/investmoz-homologacao/releases/e1f010f941e9993645e131b34ec1f4c3fe82c71b-e905ae1f",
"manifest_sha256": "e72d2fc5343a7c3bb0f9c4b2f3f3c7660b3c6e2db5ba4995875c8130d021fd68",
"database_migrations": 0,
"content_imports": 0,
"permission_changes": 0,
"git": "Fast-forward integration; no remote-exclusive main changes; no force push or patch reapplication",
"initial_failure": "115c7731 preserved the active release: one report test detected omission of an associated inactive historical document. Corrected in 7c1f5321, retaining history without requiring the inapplicable document.",
"verification": {
"affected_local_tests": 17,
"types": "passed",
"automatic_frontend_tests": {"total": 204, "passed": 204, "failed": 0},
"served_revision": "Exact commit confirmed through public build/revision.json",
"public_home": "HTTP 200 without environmental HTTP authentication",
"portfolio": {"public_records": 29, "missing_approved_ids": 0, "changed_approved_titles": 0},
"assets": "Detail, context entry/editor and simulator assets HTTP 200",
"source_hashes": "Five affected server/report files match local Git release bytes",
"persistent_runtime": "Existing environment and storage remain symlinks; no replacement of data, uploads or secrets",
"private_workflow": "Accepted synthetic QA proofs reused; no real dossier writes or new destination business-flow test"
},
"rollback": "Existing pipeline rollback to preserved previous release; retain database, encrypted payloads, uploads, permissions and later history; no database restore",
"remaining": "Transversal adaptation of the rest of the portal is partial; absent portfolio associations are not inferred"
}
@@ -0,0 +1,34 @@
{
"functional_revision": "20349299",
"catalogue_url": "https://teste-investidor.tempo.co.mz/oportunidades?sector=agronegocio&view=corridors",
"homologation_browser_confirmed": true,
"public_opportunities": 34,
"groups": {
"Corredor de Pemba–Lichinga": 5,
"Corredor de Nacala": 6,
"Corredor do Vale do Zambeze": 6,
"Corredor da Beira": 7,
"Corredor do Limpopo": 5,
"Corredor de Maputo": 5
},
"browser_check": {
"selected_ids": [54, 64],
"corridors": ["Corredor da Beira", "Corredor de Maputo"],
"chain_filter": "Soja",
"filtered_beira_opportunities": 1,
"selection_retained_after_filter": true,
"existing_path": "/investimentos/iniciar?opportunities[]=54&opportunities[]=64",
"agribusiness_top_link_confirmed": true,
"screenshot": "outputs/preservation-inventory-20261023/agribusiness-corridor-explorer-homologation.png"
},
"qa_checks": {"tests": 2, "assertions": 26, "types": "passed", "build": "passed", "comparison_between_corridors": "confirmed in browser"},
"data_writes": false,
"migrations": 0,
"imports": 0,
"republication": false,
"limitations": [
"Province and district of implantation remain unidentified for these 34 records; no corridor boundary or administrative location is inferred.",
"Comparison reuses its existing capacity of four records; preparation can include more choices.",
"Production activities use the accepted source text rather than invented stage classifications."
]
}
@@ -0,0 +1,326 @@
{
"target": {
"database": "teste_investidor",
"port": 5432
},
"tables": 174,
"changed_tables": {
"audit_events": {
"before": {
"rows": 520,
"checksum": "abeaf35ec4463ed7458f2e329a321edd"
},
"after": {
"rows": 622,
"checksum": "3518cc7405c62ad9be2f90da444fd3fe"
}
},
"cache": {
"before": {
"rows": 101,
"checksum": "6fdb8f22e7c00074aba9e7e2c7167ec0"
},
"after": {
"rows": 102,
"checksum": "d57b633a1293ed831a702d26f58f133c"
}
},
"opportunities": {
"before": {
"rows": 30,
"checksum": "8b55b9b474aa78562b40380698981fb6"
},
"after": {
"rows": 64,
"checksum": "6407d8192bb667003087a06aa3f0930f"
}
},
"sessions": {
"before": {
"rows": 24,
"checksum": "ee7deebc29696fe68a4bb1ab98c81215"
},
"after": {
"rows": 25,
"checksum": "0f41a35219cea6347427275b32897ad8"
}
}
},
"registered": 34,
"published": 34,
"records": [
{
"id": 31,
"title": "Batata, trigo, feijão, milho e soja — Corredor de Pemba–Lichinga",
"slug": "inv-mz-gen-7676707848914068",
"key": "80858ef1-42ad-5bc6-88ee-0a4a6253f9e2",
"status": "published"
},
{
"id": 32,
"title": "Algodão — Corredor de Pemba–Lichinga",
"slug": "inv-mz-gen-9477691804857755",
"key": "29990fe3-2453-5308-8a23-27622209ab02",
"status": "published"
},
{
"id": 33,
"title": "Sésamo — Corredor de Pemba–Lichinga",
"slug": "inv-mz-gen-7401258130600878",
"key": "9e79c4ac-01d5-5d20-abf3-b49df6ef8206",
"status": "published"
},
{
"id": 36,
"title": "Mandioca — Corredor de Nacala",
"slug": "inv-mz-gen-1439048955084540",
"key": "4be5f275-3504-5a7e-8ba9-d51a51efd534",
"status": "published"
},
{
"id": 38,
"title": "Algodão — Corredor de Nacala",
"slug": "inv-mz-gen-9644546880756388",
"key": "b337b1cf-01ae-5c35-9949-90704ea68f0f",
"status": "published"
},
{
"id": 39,
"title": "Frutas e caju — Corredor de Nacala",
"slug": "inv-mz-gen-2468979899503991",
"key": "1e9d0e7a-9c63-58cb-9951-349c9cc236c6",
"status": "published"
},
{
"id": 40,
"title": "Florestas — Corredor de Nacala",
"slug": "inv-mz-gen-7039993456771493",
"key": "ebd761cb-e793-5d11-809a-dc04e4baeb9d",
"status": "published"
},
{
"id": 41,
"title": "Avicultura — Corredor de Nacala",
"slug": "inv-mz-gen-5080757788800956",
"key": "69d7e89a-bd72-574c-8ce1-104f4b8448d4",
"status": "published"
},
{
"id": 42,
"title": "Arroz — Corredor do Vale do Zambeze",
"slug": "inv-mz-gen-5601915696649995",
"key": "405f0e8a-973f-5a2f-bbe8-f923944bcde1",
"status": "published"
},
{
"id": 43,
"title": "Milho e batata — Corredor do Vale do Zambeze",
"slug": "inv-mz-gen-2709717524887763",
"key": "7002d77b-2df6-5373-91b0-a67abed64378",
"status": "published"
},
{
"id": 44,
"title": "Sésamo — Corredor do Vale do Zambeze",
"slug": "inv-mz-gen-9164411293252209",
"key": "4bfe5b57-ea81-564e-a6ec-8867de5b79f7",
"status": "published"
},
{
"id": 45,
"title": "Algodão — Corredor do Vale do Zambeze",
"slug": "inv-mz-gen-3014063241101625",
"key": "40c73a69-8724-5552-9612-ecd9ec50d6d8",
"status": "published"
},
{
"id": 46,
"title": "Avicultura — Corredor do Vale do Zambeze",
"slug": "inv-mz-gen-6214131825033367",
"key": "d8ab3e05-a180-5a0a-877e-c9b4fab34931",
"status": "published"
},
{
"id": 48,
"title": "Milho, trigo e arroz — Corredor da Beira",
"slug": "inv-mz-gen-6048328347519124",
"key": "a80dc6b8-1c9b-53ef-bf31-ec37fa22561f",
"status": "published"
},
{
"id": 49,
"title": "Soja — Corredor da Beira",
"slug": "inv-mz-gen-4587508237495307",
"key": "59726827-4fe9-5256-af12-10bd56dab0e3",
"status": "published"
},
{
"id": 52,
"title": "Açúcar — Corredor da Beira",
"slug": "inv-mz-gen-1634854290375763",
"key": "bc1ee2df-33f9-5262-9ad4-1af961393c88",
"status": "published"
},
{
"id": 53,
"title": "Florestas — Corredor da Beira",
"slug": "inv-mz-gen-8326396601945337",
"key": "aaf5661e-0a45-561a-b74e-c5cbfaad7721",
"status": "published"
},
{
"id": 55,
"title": "Arroz — Corredor do Limpopo",
"slug": "inv-mz-gen-4222404773242970",
"key": "0ebbae53-7331-5360-b704-3060f2f52548",
"status": "published"
},
{
"id": 37,
"title": "Milho, amendoim e sésamo — Corredor de Nacala",
"slug": "inv-mz-gen-5107565297512781",
"key": "7887e378-6d59-5acc-94e1-41f320e690f6",
"status": "published"
},
{
"id": 56,
"title": "Hortícolas — Corredor do Limpopo",
"slug": "inv-mz-gen-2333485246312757",
"key": "5ead8334-a1ea-5710-b5ae-751e1a97bc18",
"status": "published"
},
{
"id": 58,
"title": "Avicultura — Corredor do Limpopo",
"slug": "inv-mz-gen-9065431632626708",
"key": "7b4070c4-59f9-59f3-ae3a-ef897998e5ae",
"status": "published"
},
{
"id": 59,
"title": "Infraestruturas e serviços de apoio — Corredor do Limpopo",
"slug": "inv-mz-gen-5245892626712728",
"key": "09fa3da3-96c0-562c-8b3d-1010841fac16",
"status": "published"
},
{
"id": 60,
"title": "Arroz — Corredor de Maputo",
"slug": "inv-mz-gen-8724580943323815",
"key": "8b3cd51b-d51a-5a60-9ec3-9222db943bbb",
"status": "published"
},
{
"id": 63,
"title": "Avicultura — Corredor de Maputo",
"slug": "inv-mz-gen-4404538462410084",
"key": "a15d68d3-02c8-50d9-a03b-63f0478733ff",
"status": "published"
},
{
"id": 34,
"title": "Florestas — Corredor de Pemba–Lichinga",
"slug": "inv-mz-gen-2816022300638626",
"key": "ccde9a38-0a27-5b46-a158-b2013128baaf",
"status": "published"
},
{
"id": 35,
"title": "Avicultura — Corredor de Pemba–Lichinga",
"slug": "inv-mz-gen-6844264076753489",
"key": "1748aee6-cf7a-5e16-acde-d37777578d3a",
"status": "published"
},
{
"id": 47,
"title": "Bovinos e caprinos — Corredor do Vale do Zambeze",
"slug": "inv-mz-gen-5894491385867554",
"key": "8a493e37-5d26-5cc7-9937-aab3e7ad0f40",
"status": "published"
},
{
"id": 50,
"title": "Hortícolas — Corredor da Beira",
"slug": "inv-mz-gen-3954947238505605",
"key": "2d0fb383-6d02-5df6-a24a-57474ab15ad0",
"status": "published"
},
{
"id": 51,
"title": "Sésamo — Corredor da Beira",
"slug": "inv-mz-gen-8142311444413640",
"key": "791e2921-92cf-5e08-9ff7-99a275bd6f34",
"status": "published"
},
{
"id": 54,
"title": "Carne bovina — Corredor da Beira",
"slug": "inv-mz-gen-3963957640266320",
"key": "45423b00-4673-5595-b3ac-cc4ab8e1dc8a",
"status": "published"
},
{
"id": 57,
"title": "Carne vermelha — Corredor do Limpopo",
"slug": "inv-mz-gen-4521723858186670",
"key": "fbbb0961-e5b0-5900-ad33-4efb5fc7bfc5",
"status": "published"
},
{
"id": 61,
"title": "Hortícolas — Corredor de Maputo",
"slug": "inv-mz-gen-3403417739078882",
"key": "14b76956-6afa-5c52-8d56-db402dbbd099",
"status": "published"
},
{
"id": 62,
"title": "Carne vermelha — Corredor de Maputo",
"slug": "inv-mz-gen-6923224180891809",
"key": "bd07739d-e0b8-5561-8baa-14f67571ecf5",
"status": "published"
},
{
"id": 64,
"title": "Infraestruturas e serviços de apoio — Corredor de Maputo",
"slug": "inv-mz-gen-7371026296970534",
"key": "eb5bad23-5ea2-5f81-bb2e-85609853ae1b",
"status": "published"
}
],
"resources": 59,
"geometries": 408,
"preexisting_opportunities_preserved": true,
"comparison_reader": "postgres, same identity as backup; RLS-filtered application counts are not used as loss evidence",
"served_revision": {
"commit": "e12ef6ad426ff6ebc4e7421a1ddbe7f184d77a55",
"branch": "main",
"built_at": "2026-10-23T09:01:54.733542+00:00",
"manifest_sha256": "a7fb08d79db71f19f0dde06c8ebb9329cfaf8e93c2f5a80f460aef98aa885398"
},
"installation_complete": true,
"registration_complete": true,
"publication_complete": true,
"pending": null,
"backup_restore": {
"verified": true,
"tables": 174,
"files": 369,
"dump_sha256": "318fcdce6623f1032fa319372a08ff4219f4def509f64cf73b39674e8b85260e",
"files_sha256": "b0903725916a9b523a0f90c10ae6c42877f4374fb711f900f19e0b2053606536"
},
"management_url": "https://teste-investidor.tempo.co.mz/minhas-oportunidades/lotes/agronegocio",
"comparison_correction": "Initial application read was RLS-filtered; equivalent postgres read confirmed all simulation records preserved. No data restoration or security policy changes executed.",
"execution": "Ordinary authenticated real-account browser interface, preview and confirmation, existing recent MFA. No console registration or publication.",
"public_url": "https://teste-investidor.tempo.co.mz/oportunidades?sector=agronegocio",
"browser_proof": {
"results": 34,
"selected_ids": [
64,
63
],
"path": "/investimentos/iniciar?opportunities[]=64&opportunities[]=63",
"screenshot": "outputs/preservation-inventory-20261023/agribusiness-homologation-published.png"
},
"unchanged_tables": 170
}
@@ -0,0 +1,214 @@
{
"target": {
"name": "simulator_fiscal_delta_20261021_test",
"port": 55441
},
"source_sha256": "1473b06c48aa234e771660e3f2a51b9d2e4e1943bd52d878e0ae854ee6657055",
"records": [
{
"id": 269,
"created": true,
"key": "80858ef1-42ad-5bc6-88ee-0a4a6253f9e2"
},
{
"id": 270,
"created": true,
"key": "29990fe3-2453-5308-8a23-27622209ab02"
},
{
"id": 271,
"created": true,
"key": "9e79c4ac-01d5-5d20-abf3-b49df6ef8206"
},
{
"id": 272,
"created": true,
"key": "ccde9a38-0a27-5b46-a158-b2013128baaf"
},
{
"id": 273,
"created": true,
"key": "1748aee6-cf7a-5e16-acde-d37777578d3a"
},
{
"id": 274,
"created": true,
"key": "4be5f275-3504-5a7e-8ba9-d51a51efd534"
},
{
"id": 275,
"created": true,
"key": "7887e378-6d59-5acc-94e1-41f320e690f6"
},
{
"id": 276,
"created": true,
"key": "b337b1cf-01ae-5c35-9949-90704ea68f0f"
},
{
"id": 277,
"created": true,
"key": "1e9d0e7a-9c63-58cb-9951-349c9cc236c6"
},
{
"id": 278,
"created": true,
"key": "ebd761cb-e793-5d11-809a-dc04e4baeb9d"
},
{
"id": 279,
"created": true,
"key": "69d7e89a-bd72-574c-8ce1-104f4b8448d4"
},
{
"id": 280,
"created": true,
"key": "405f0e8a-973f-5a2f-bbe8-f923944bcde1"
},
{
"id": 281,
"created": true,
"key": "7002d77b-2df6-5373-91b0-a67abed64378"
},
{
"id": 282,
"created": true,
"key": "4bfe5b57-ea81-564e-a6ec-8867de5b79f7"
},
{
"id": 283,
"created": true,
"key": "40c73a69-8724-5552-9612-ecd9ec50d6d8"
},
{
"id": 284,
"created": true,
"key": "d8ab3e05-a180-5a0a-877e-c9b4fab34931"
},
{
"id": 285,
"created": true,
"key": "8a493e37-5d26-5cc7-9937-aab3e7ad0f40"
},
{
"id": 286,
"created": true,
"key": "a80dc6b8-1c9b-53ef-bf31-ec37fa22561f"
},
{
"id": 287,
"created": true,
"key": "59726827-4fe9-5256-af12-10bd56dab0e3"
},
{
"id": 288,
"created": true,
"key": "2d0fb383-6d02-5df6-a24a-57474ab15ad0"
},
{
"id": 289,
"created": true,
"key": "791e2921-92cf-5e08-9ff7-99a275bd6f34"
},
{
"id": 290,
"created": true,
"key": "bc1ee2df-33f9-5262-9ad4-1af961393c88"
},
{
"id": 291,
"created": true,
"key": "aaf5661e-0a45-561a-b74e-c5cbfaad7721"
},
{
"id": 292,
"created": true,
"key": "45423b00-4673-5595-b3ac-cc4ab8e1dc8a"
},
{
"id": 293,
"created": true,
"key": "0ebbae53-7331-5360-b704-3060f2f52548"
},
{
"id": 294,
"created": true,
"key": "5ead8334-a1ea-5710-b5ae-751e1a97bc18"
},
{
"id": 295,
"created": true,
"key": "fbbb0961-e5b0-5900-ad33-4efb5fc7bfc5"
},
{
"id": 296,
"created": true,
"key": "7b4070c4-59f9-59f3-ae3a-ef897998e5ae"
},
{
"id": 297,
"created": true,
"key": "09fa3da3-96c0-562c-8b3d-1010841fac16"
},
{
"id": 298,
"created": true,
"key": "8b3cd51b-d51a-5a60-9ec3-9222db943bbb"
},
{
"id": 299,
"created": true,
"key": "14b76956-6afa-5c52-8d56-db402dbbd099"
},
{
"id": 300,
"created": true,
"key": "bd07739d-e0b8-5561-8baa-14f67571ecf5"
},
{
"id": 301,
"created": true,
"key": "a15d68d3-02c8-50d9-a03b-63f0478733ff"
},
{
"id": 302,
"created": true,
"key": "eb5bad23-5ea2-5f81-bb2e-85609853ae1b"
}
],
"total": 34,
"new": 34,
"published": 0,
"active_writes": 0,
"status": "QA private registration complete; homologation unchanged",
"preservation": {
"target": {
"name": "simulator_fiscal_delta_20261021_test",
"port": 55441
},
"records": 34,
"private_drafts": 34,
"preserved": {
"opportunities": true,
"audit_events": true,
"development_corridors": true,
"portal_role_assignments": true,
"portal_resources": true,
"documents": true
},
"active_writes": 0
},
"checks": {
"feature_tests": 3,
"assertions": 553,
"types": "passed",
"frontend_build": "passed",
"test_disclosures_rolled_back": true
},
"management_url": "http://127.0.0.1:18574/gestao-conteudos",
"individual_editor_example": "http://127.0.0.1:18574/minhas-oportunidades/269/edit",
"reused_promotional_candidate": "9d3bfa89",
"schema_changes": 0,
"new_roles": 0,
"new_dependencies": 0
}
@@ -0,0 +1,246 @@
{
"target": {
"name": "simulator_fiscal_delta_20261021_test",
"port": 55441
},
"published": 34,
"authentication": "QA role simulation only; no production authentication claimed",
"source_sha256": "1473b06c48aa234e771660e3f2a51b9d2e4e1943bd52d878e0ae854ee6657055",
"records": [
{
"id": 269,
"title": "Batata, trigo, feijão, milho e soja — Corredor de Pemba–Lichinga",
"corridor": "Corredor de Pemba–Lichinga",
"url": "/oportunidades/inv-mz-gen-5150397406444731"
},
{
"id": 270,
"title": "Algodão — Corredor de Pemba–Lichinga",
"corridor": "Corredor de Pemba–Lichinga",
"url": "/oportunidades/inv-mz-gen-1647249176453799"
},
{
"id": 271,
"title": "Sésamo — Corredor de Pemba–Lichinga",
"corridor": "Corredor de Pemba–Lichinga",
"url": "/oportunidades/inv-mz-gen-3352774267583285"
},
{
"id": 272,
"title": "Florestas — Corredor de Pemba–Lichinga",
"corridor": "Corredor de Pemba–Lichinga",
"url": "/oportunidades/inv-mz-gen-7850139886806206"
},
{
"id": 273,
"title": "Avicultura — Corredor de Pemba–Lichinga",
"corridor": "Corredor de Pemba–Lichinga",
"url": "/oportunidades/inv-mz-gen-3839727014705762"
},
{
"id": 274,
"title": "Mandioca — Corredor de Nacala",
"corridor": "Corredor de Nacala",
"url": "/oportunidades/inv-mz-gen-8963722873273976"
},
{
"id": 275,
"title": "Milho, amendoim e sésamo — Corredor de Nacala",
"corridor": "Corredor de Nacala",
"url": "/oportunidades/inv-mz-gen-9998208319499345"
},
{
"id": 276,
"title": "Algodão — Corredor de Nacala",
"corridor": "Corredor de Nacala",
"url": "/oportunidades/inv-mz-gen-6731688736856562"
},
{
"id": 277,
"title": "Frutas e caju — Corredor de Nacala",
"corridor": "Corredor de Nacala",
"url": "/oportunidades/inv-mz-gen-2325832878312017"
},
{
"id": 278,
"title": "Florestas — Corredor de Nacala",
"corridor": "Corredor de Nacala",
"url": "/oportunidades/inv-mz-gen-8943819075565910"
},
{
"id": 279,
"title": "Avicultura — Corredor de Nacala",
"corridor": "Corredor de Nacala",
"url": "/oportunidades/inv-mz-gen-3960434222994703"
},
{
"id": 280,
"title": "Arroz — Corredor do Vale do Zambeze",
"corridor": "Corredor do Vale do Zambeze",
"url": "/oportunidades/inv-mz-gen-4676568685660682"
},
{
"id": 281,
"title": "Milho e batata — Corredor do Vale do Zambeze",
"corridor": "Corredor do Vale do Zambeze",
"url": "/oportunidades/inv-mz-gen-9955994100950689"
},
{
"id": 282,
"title": "Sésamo — Corredor do Vale do Zambeze",
"corridor": "Corredor do Vale do Zambeze",
"url": "/oportunidades/inv-mz-gen-5272341950046110"
},
{
"id": 283,
"title": "Algodão — Corredor do Vale do Zambeze",
"corridor": "Corredor do Vale do Zambeze",
"url": "/oportunidades/inv-mz-gen-5628419400094441"
},
{
"id": 284,
"title": "Avicultura — Corredor do Vale do Zambeze",
"corridor": "Corredor do Vale do Zambeze",
"url": "/oportunidades/inv-mz-gen-8668117557933061"
},
{
"id": 285,
"title": "Bovinos e caprinos — Corredor do Vale do Zambeze",
"corridor": "Corredor do Vale do Zambeze",
"url": "/oportunidades/inv-mz-gen-6651453468338687"
},
{
"id": 286,
"title": "Milho, trigo e arroz — Corredor da Beira",
"corridor": "Corredor da Beira",
"url": "/oportunidades/inv-mz-gen-8128691124566423"
},
{
"id": 287,
"title": "Soja — Corredor da Beira",
"corridor": "Corredor da Beira",
"url": "/oportunidades/inv-mz-gen-5563232446855414"
},
{
"id": 288,
"title": "Hortícolas — Corredor da Beira",
"corridor": "Corredor da Beira",
"url": "/oportunidades/inv-mz-gen-5715109945209502"
},
{
"id": 289,
"title": "Sésamo — Corredor da Beira",
"corridor": "Corredor da Beira",
"url": "/oportunidades/inv-mz-gen-6307033318408482"
},
{
"id": 290,
"title": "Açúcar — Corredor da Beira",
"corridor": "Corredor da Beira",
"url": "/oportunidades/inv-mz-gen-8123001467176624"
},
{
"id": 291,
"title": "Florestas — Corredor da Beira",
"corridor": "Corredor da Beira",
"url": "/oportunidades/inv-mz-gen-5629890674429876"
},
{
"id": 292,
"title": "Carne bovina — Corredor da Beira",
"corridor": "Corredor da Beira",
"url": "/oportunidades/inv-mz-gen-2472671405287788"
},
{
"id": 293,
"title": "Arroz — Corredor do Limpopo",
"corridor": "Corredor do Limpopo",
"url": "/oportunidades/inv-mz-gen-5995177607070005"
},
{
"id": 294,
"title": "Hortícolas — Corredor do Limpopo",
"corridor": "Corredor do Limpopo",
"url": "/oportunidades/inv-mz-gen-7967808283045554"
},
{
"id": 295,
"title": "Carne vermelha — Corredor do Limpopo",
"corridor": "Corredor do Limpopo",
"url": "/oportunidades/inv-mz-gen-5036463681202017"
},
{
"id": 296,
"title": "Avicultura — Corredor do Limpopo",
"corridor": "Corredor do Limpopo",
"url": "/oportunidades/inv-mz-gen-5524442722730781"
},
{
"id": 297,
"title": "Infraestruturas e serviços de apoio — Corredor do Limpopo",
"corridor": "Corredor do Limpopo",
"url": "/oportunidades/inv-mz-gen-2099729870926704"
},
{
"id": 298,
"title": "Arroz — Corredor de Maputo",
"corridor": "Corredor de Maputo",
"url": "/oportunidades/inv-mz-gen-1866403697817634"
},
{
"id": 299,
"title": "Hortícolas — Corredor de Maputo",
"corridor": "Corredor de Maputo",
"url": "/oportunidades/inv-mz-gen-6293243388893029"
},
{
"id": 300,
"title": "Carne vermelha — Corredor de Maputo",
"corridor": "Corredor de Maputo",
"url": "/oportunidades/inv-mz-gen-9330026487459372"
},
{
"id": 301,
"title": "Avicultura — Corredor de Maputo",
"corridor": "Corredor de Maputo",
"url": "/oportunidades/inv-mz-gen-1761515912486377"
},
{
"id": 302,
"title": "Infraestruturas e serviços de apoio — Corredor de Maputo",
"corridor": "Corredor de Maputo",
"url": "/oportunidades/inv-mz-gen-2643789930780297"
}
],
"active_writes": 0,
"checks": {
"context_tests": 7,
"context_assertions": 132,
"batch_tests": 1,
"batch_assertions": 15,
"frontend_types": "passed",
"frontend_build": "passed",
"browser_catalogue_results": 34,
"browser_selected_opportunities": 2
},
"preservation": {
"target": {
"name": "simulator_fiscal_delta_20261021_test",
"port": 55441
},
"records": 34,
"private_drafts": 0,
"preserved": {
"opportunities": true,
"audit_events": true,
"development_corridors": true,
"portal_role_assignments": true,
"portal_resources": true,
"documents": true
},
"active_writes": 0,
"published_in_qa": 34
},
"qa_catalogue_url": "http://127.0.0.1:18574/oportunidades?sector=agronegocio",
"homologation_status": "Backup/restore verified; code delivery and authenticated registration/disclosure still separate."
}
@@ -0,0 +1,36 @@
{
"url": "https://teste-investidor.tempo.co.mz/agronegocio",
"status": 200,
"served_revision": "20a4099d10400496e19f03589b6383f512b48d9d",
"sections": [
{
"name": "Corredor de Pemba–Lichinga",
"entries": 5
},
{
"name": "Corredor de Nacala",
"entries": 6
},
{
"name": "Corredor do Vale do Zambeze",
"entries": 6
},
{
"name": "Corredor da Beira",
"entries": 7
},
{
"name": "Corredor do Limpopo",
"entries": 5
},
{
"name": "Corredor de Maputo",
"entries": 5
}
],
"entries": 34,
"original_html_preserved": true,
"metadata_image_version_source_preserved": true,
"article_sha256": "0929184a6d4645b9046512675394ad4f60575273f6dab0955f71cc7474a6a4e2",
"database_writes": 0
}
@@ -0,0 +1,358 @@
{
"format": "investmoz-approved-portfolio-visibility-recovery-v1",
"request": "Tempo, 23/10/2026: inventariar funcionalidades concluídas, inventariar desaparecidas e repor, preservando o aprovado",
"scope": "Restore visibility of exact unchanged previously owner-approved dossiers, not approve new or modified content",
"source_database": "investemoz",
"source_snapshot_sha256": "c27a34e30617ce56164daab0aea5cdd7e43b8bec9b2fa197ef616e6c87e70575",
"historic_snapshot_sha256": "6904486ee9c3895ee5e42d9ef19e6cb5f223930f7734a7af18051053d019cda6",
"items": [
{
"code": "A01",
"title": "MozParks – 20 Parques Industriais",
"source_record_id": "219e1dbb-939c-4ef0-92b7-7f78f8561fa0",
"target_record_id": "9871a8c0-d562-420b-a8f7-174c95db93f0",
"expected_target_version": 1,
"content_sha256": "2adcbe741777d4352c47eb31281dc6b2e2d04903fcfd3e59778ddff4e1342977",
"source_sha256": "4919cd8aae6230e576947b38e5ff839b2661166fb2a8598bea1d662f15bd170a",
"approval_evidence": "26/09/2026 — Aprovação expressa do proprietário nesta sessão: inserir a informação do documento upgrade do portal de investimentos v4 programacao sobre clusters, corredores e projectos ancora nas tabelas e criar os caminhos para a sua execução; eu e que aprovo e estou a aprovar.",
"approval_sha256": "0cbc4b7a3f01dfb6c28659a11597f7e10f87f8c3119bf44ea92b97bec033f3eb",
"original_published_at": "2026-09-26 16:22:52+00"
},
{
"code": "A02",
"title": "Linha Ferroviária Norte–Sul",
"source_record_id": "1125f069-61e2-4071-99f3-dd103caf3156",
"target_record_id": "64b824a0-3527-4024-a1a2-5234f5b4caaa",
"expected_target_version": 1,
"content_sha256": "3ad808813909f1475f44a5fcf2a4e50c5d01e25ad8ea39d906c52519532cf0ee",
"source_sha256": "4919cd8aae6230e576947b38e5ff839b2661166fb2a8598bea1d662f15bd170a",
"approval_evidence": "26/09/2026 — Aprovação expressa do proprietário nesta sessão: inserir a informação do documento upgrade do portal de investimentos v4 programacao sobre clusters, corredores e projectos ancora nas tabelas e criar os caminhos para a sua execução; eu e que aprovo e estou a aprovar.",
"approval_sha256": "0cbc4b7a3f01dfb6c28659a11597f7e10f87f8c3119bf44ea92b97bec033f3eb",
"original_published_at": "2026-09-26 16:22:52+00"
},
{
"code": "A03",
"title": "Programa de Desenvolvimento Integrado do Corredor de Nacala",
"source_record_id": "39f5973c-f4cd-49d0-aeea-1c0c71783550",
"target_record_id": "6c03e980-c455-49cc-a3be-99d29014e91d",
"expected_target_version": 1,
"content_sha256": "75266ff89da58e4d6e5422e49e4641c62dae4f171fa358b70e210c5883a1fd07",
"source_sha256": "4919cd8aae6230e576947b38e5ff839b2661166fb2a8598bea1d662f15bd170a",
"approval_evidence": "26/09/2026 — Aprovação expressa do proprietário nesta sessão: inserir a informação do documento upgrade do portal de investimentos v4 programacao sobre clusters, corredores e projectos ancora nas tabelas e criar os caminhos para a sua execução; eu e que aprovo e estou a aprovar.",
"approval_sha256": "0cbc4b7a3f01dfb6c28659a11597f7e10f87f8c3119bf44ea92b97bec033f3eb",
"original_published_at": "2026-09-26 16:22:52+00"
},
{
"code": "A04",
"title": "Crusse Jamali – Zona Económica Especial de Turismo",
"source_record_id": "0dccdd26-9796-4ef7-947b-5a2970ace379",
"target_record_id": "8bd83cc4-b36c-46cd-9287-b47e1162b578",
"expected_target_version": 1,
"content_sha256": "f6a63e2ee2f2e7c98f1030d206996ad57b0d92129d021ed3c6bb53f161d56f5b",
"source_sha256": "4919cd8aae6230e576947b38e5ff839b2661166fb2a8598bea1d662f15bd170a",
"approval_evidence": "26/09/2026 — Aprovação expressa do proprietário nesta sessão: inserir a informação do documento upgrade do portal de investimentos v4 programacao sobre clusters, corredores e projectos ancora nas tabelas e criar os caminhos para a sua execução; eu e que aprovo e estou a aprovar.",
"approval_sha256": "0cbc4b7a3f01dfb6c28659a11597f7e10f87f8c3119bf44ea92b97bec033f3eb",
"original_published_at": "2026-09-26 16:22:52+00"
},
{
"code": "A05",
"title": "Linha de Transmissão Namialo–Metoro (400 kV)",
"source_record_id": "361789ad-1e14-4b64-8f1f-fad053886d5a",
"target_record_id": "d8a88bac-4684-43fa-aea6-e54fef9b8a47",
"expected_target_version": 1,
"content_sha256": "ea0f6b7843289d81dc2852327432721f8677e28ad8b41d754abc8c91307303d3",
"source_sha256": "4919cd8aae6230e576947b38e5ff839b2661166fb2a8598bea1d662f15bd170a",
"approval_evidence": "26/09/2026 — Aprovação expressa do proprietário nesta sessão: inserir a informação do documento upgrade do portal de investimentos v4 programacao sobre clusters, corredores e projectos ancora nas tabelas e criar os caminhos para a sua execução; eu e que aprovo e estou a aprovar.",
"approval_sha256": "0cbc4b7a3f01dfb6c28659a11597f7e10f87f8c3119bf44ea92b97bec033f3eb",
"original_published_at": "2026-09-26 16:22:52+00"
},
{
"code": "A06",
"title": "Cadeia de Valor do Arroz por Projecto de Processamento",
"source_record_id": "15474e6a-90a3-4db4-b456-eb1c1c66227c",
"target_record_id": "57a4ee7a-4c07-4e26-b8f5-e07726e41512",
"expected_target_version": 1,
"content_sha256": "c7fae7b2ca541e734e587589dace8dc85dee2494876de6e30ecd91a7177a985d",
"source_sha256": "4919cd8aae6230e576947b38e5ff839b2661166fb2a8598bea1d662f15bd170a",
"approval_evidence": "26/09/2026 — Aprovação expressa do proprietário nesta sessão: inserir a informação do documento upgrade do portal de investimentos v4 programacao sobre clusters, corredores e projectos ancora nas tabelas e criar os caminhos para a sua execução; eu e que aprovo e estou a aprovar.",
"approval_sha256": "0cbc4b7a3f01dfb6c28659a11597f7e10f87f8c3119bf44ea92b97bec033f3eb",
"original_published_at": "2026-09-26 16:22:52+00"
},
{
"code": "A07",
"title": "Parque Têxtil Integrado",
"source_record_id": "22323abb-91d9-43ed-98f3-1194a5250c9e",
"target_record_id": "70ae731e-fdef-473f-b495-14f319b28d60",
"expected_target_version": 1,
"content_sha256": "3eb01ce3dd2cc68b43b816708f7e5f990c48f1aea6555cdfd169a286e11b9bd4",
"source_sha256": "4919cd8aae6230e576947b38e5ff839b2661166fb2a8598bea1d662f15bd170a",
"approval_evidence": "26/09/2026 — Aprovação expressa do proprietário nesta sessão: inserir a informação do documento upgrade do portal de investimentos v4 programacao sobre clusters, corredores e projectos ancora nas tabelas e criar os caminhos para a sua execução; eu e que aprovo e estou a aprovar.",
"approval_sha256": "0cbc4b7a3f01dfb6c28659a11597f7e10f87f8c3119bf44ea92b97bec033f3eb",
"original_published_at": "2026-09-26 16:22:52+00"
},
{
"code": "A08",
"title": "Zona Económica Especial de Mocuba",
"source_record_id": "81d775e5-5dff-4fa2-98b1-72affb0a1082",
"target_record_id": "b30ecd8c-220d-4c7a-b1a7-704dc24d0933",
"expected_target_version": 1,
"content_sha256": "bca089a4bb2601ca34c50e2c48068b4c2f9fa7a873955cf0587f261a83c2a146",
"source_sha256": "4919cd8aae6230e576947b38e5ff839b2661166fb2a8598bea1d662f15bd170a",
"approval_evidence": "26/09/2026 — Aprovação expressa do proprietário nesta sessão: inserir a informação do documento upgrade do portal de investimentos v4 programacao sobre clusters, corredores e projectos ancora nas tabelas e criar os caminhos para a sua execução; eu e que aprovo e estou a aprovar.",
"approval_sha256": "0cbc4b7a3f01dfb6c28659a11597f7e10f87f8c3119bf44ea92b97bec033f3eb",
"original_published_at": "2026-09-26 16:22:52+00"
},
{
"code": "A09",
"title": "Construção da Cidadela Parlamentar de Mocuba",
"source_record_id": "67263220-6849-468f-82f4-d5e1f5617190",
"target_record_id": "dc8892ff-fdda-480c-81f1-2ffe0bfcb40b",
"expected_target_version": 1,
"content_sha256": "7e3bb071f65e49245fbfd070f4123ac49985004e865c475bf7e34c85cc708aea",
"source_sha256": "4919cd8aae6230e576947b38e5ff839b2661166fb2a8598bea1d662f15bd170a",
"approval_evidence": "26/09/2026 — Aprovação expressa do proprietário nesta sessão: inserir a informação do documento upgrade do portal de investimentos v4 programacao sobre clusters, corredores e projectos ancora nas tabelas e criar os caminhos para a sua execução; eu e que aprovo e estou a aprovar.",
"approval_sha256": "0cbc4b7a3f01dfb6c28659a11597f7e10f87f8c3119bf44ea92b97bec033f3eb",
"original_published_at": "2026-09-26 16:22:52+00"
},
{
"code": "A10",
"title": "Mphanda Nkuwa HPP – Hidroeléctrica",
"source_record_id": "cad01a02-6072-4a11-823a-1ae379bc65da",
"target_record_id": "77b685f0-848f-4800-83f8-a29539ed8f31",
"expected_target_version": 1,
"content_sha256": "02dc0fb152908e81b11304f6bc0c2252805ebd2ad1860b1a6bfa918e2cbcd5bb",
"source_sha256": "4919cd8aae6230e576947b38e5ff839b2661166fb2a8598bea1d662f15bd170a",
"approval_evidence": "26/09/2026 — Aprovação expressa do proprietário nesta sessão: inserir a informação do documento upgrade do portal de investimentos v4 programacao sobre clusters, corredores e projectos ancora nas tabelas e criar os caminhos para a sua execução; eu e que aprovo e estou a aprovar.",
"approval_sha256": "0cbc4b7a3f01dfb6c28659a11597f7e10f87f8c3119bf44ea92b97bec033f3eb",
"original_published_at": "2026-09-26 16:22:52+00"
},
{
"code": "A11",
"title": "Corredor Ferroviário e Portuário de Macuse–Moatize",
"source_record_id": "72c13e83-e8dd-465d-99a9-eb0ccaa4fd0b",
"target_record_id": "cb125f84-6f75-48e5-a0de-2f5e6bd537ed",
"expected_target_version": 1,
"content_sha256": "736fc41d941740f41697c579ffe6c4208d45005d7177b7c42f006554a5dde480",
"source_sha256": "4919cd8aae6230e576947b38e5ff839b2661166fb2a8598bea1d662f15bd170a",
"approval_evidence": "26/09/2026 — Aprovação expressa do proprietário nesta sessão: inserir a informação do documento upgrade do portal de investimentos v4 programacao sobre clusters, corredores e projectos ancora nas tabelas e criar os caminhos para a sua execução; eu e que aprovo e estou a aprovar.",
"approval_sha256": "0cbc4b7a3f01dfb6c28659a11597f7e10f87f8c3119bf44ea92b97bec033f3eb",
"original_published_at": "2026-09-26 16:22:52+00"
},
{
"code": "A12",
"title": "Programa de Conectividade e Resiliência Costeira (Compacto II)",
"source_record_id": "a023d6b2-1fa2-4fe7-9709-f93ae1f10e99",
"target_record_id": "71b96599-09c2-4809-b98f-acbf0c2b6f38",
"expected_target_version": 1,
"content_sha256": "9dbdb468b8644e07fabdc943ecde2f11ea561d5174c00559228ed29b2a29b1fe",
"source_sha256": "4919cd8aae6230e576947b38e5ff839b2661166fb2a8598bea1d662f15bd170a",
"approval_evidence": "26/09/2026 — Aprovação expressa do proprietário nesta sessão: inserir a informação do documento upgrade do portal de investimentos v4 programacao sobre clusters, corredores e projectos ancora nas tabelas e criar os caminhos para a sua execução; eu e que aprovo e estou a aprovar.",
"approval_sha256": "0cbc4b7a3f01dfb6c28659a11597f7e10f87f8c3119bf44ea92b97bec033f3eb",
"original_published_at": "2026-09-26 16:22:52+00"
},
{
"code": "A13",
"title": "Vilanculos – Região Integrada de Turismo",
"source_record_id": "bb15487f-1617-49e6-86b7-d617812b658b",
"target_record_id": "5ce2d415-1c84-4625-a4cd-0f6738454318",
"expected_target_version": 1,
"content_sha256": "7a62afdda8e2c3fcc47b1c9032359906ff75dff9b5d56c71ba7d88781a8f8620",
"source_sha256": "4919cd8aae6230e576947b38e5ff839b2661166fb2a8598bea1d662f15bd170a",
"approval_evidence": "26/09/2026 — Aprovação expressa do proprietário nesta sessão: inserir a informação do documento upgrade do portal de investimentos v4 programacao sobre clusters, corredores e projectos ancora nas tabelas e criar os caminhos para a sua execução; eu e que aprovo e estou a aprovar.",
"approval_sha256": "0cbc4b7a3f01dfb6c28659a11597f7e10f87f8c3119bf44ea92b97bec033f3eb",
"original_published_at": "2026-09-26 16:22:52+00"
},
{
"code": "A14",
"title": "Zona Económica Especial de Agronegócio do Limpopo",
"source_record_id": "6d973bb4-ab8a-49bc-92ed-286ed5b96fc4",
"target_record_id": "88111fb4-ab20-4393-bd1b-3a1694fb4ac4",
"expected_target_version": 1,
"content_sha256": "930836392126133499fa69313a4718ce2ec1f6c8f0839c184e9b5cc229460d1c",
"source_sha256": "4919cd8aae6230e576947b38e5ff839b2661166fb2a8598bea1d662f15bd170a",
"approval_evidence": "26/09/2026 — Aprovação expressa do proprietário nesta sessão: inserir a informação do documento upgrade do portal de investimentos v4 programacao sobre clusters, corredores e projectos ancora nas tabelas e criar os caminhos para a sua execução; eu e que aprovo e estou a aprovar.",
"approval_sha256": "0cbc4b7a3f01dfb6c28659a11597f7e10f87f8c3119bf44ea92b97bec033f3eb",
"original_published_at": "2026-09-26 16:22:52+00"
},
{
"code": "A15",
"title": "Regadio na Bacia Hidrográfica do Rio Maputo",
"source_record_id": "1468b8d2-1b08-4c84-8000-ece7662edbf6",
"target_record_id": "88e37c63-e785-446f-9074-84b000bffa59",
"expected_target_version": 1,
"content_sha256": "d19d27451296b9816351e2d340f2aaff9fabf9732fb580d179e1c69ceb9fc512",
"source_sha256": "4919cd8aae6230e576947b38e5ff839b2661166fb2a8598bea1d662f15bd170a",
"approval_evidence": "26/09/2026 — Aprovação expressa do proprietário nesta sessão: inserir a informação do documento upgrade do portal de investimentos v4 programacao sobre clusters, corredores e projectos ancora nas tabelas e criar os caminhos para a sua execução; eu e que aprovo e estou a aprovar.",
"approval_sha256": "0cbc4b7a3f01dfb6c28659a11597f7e10f87f8c3119bf44ea92b97bec033f3eb",
"original_published_at": "2026-09-26 16:22:52+00"
},
{
"code": "CLU-01",
"title": "Agro-indústria e segurança alimentar",
"source_record_id": "ca6a1427-0e2b-406e-94bd-da23630d5ec4",
"target_record_id": "0c63d8de-18fd-451f-a7ca-1ea04dd5906c",
"expected_target_version": 1,
"content_sha256": "ccd16f969474dcc23b2ddf27eb4cf3ca090f22e418e0e8f33499f349e884d042",
"source_sha256": "70688ba4623298f5ec073331c06c28ca5b0a9c552578e22ccebe20c27d1ca8cf",
"approval_evidence": "26/09/2026 — Aprovação expressa do proprietário nesta sessão: inserir a informação do documento upgrade do portal de investimentos v4 programacao sobre clusters, corredores e projectos ancora nas tabelas e criar os caminhos para a sua execução; eu e que aprovo e estou a aprovar.",
"approval_sha256": "0cbc4b7a3f01dfb6c28659a11597f7e10f87f8c3119bf44ea92b97bec033f3eb",
"original_published_at": "2026-09-26 16:22:52+00"
},
{
"code": "CLU-02",
"title": "Energia e industrialização",
"source_record_id": "3cd11616-9099-477c-bf3e-09cddca74053",
"target_record_id": "fc1355f5-1473-4fe2-b3e2-b32d1f0b1042",
"expected_target_version": 1,
"content_sha256": "78c50659068301be0db94e8f4c9e77a20a4a052639dc4553291ff3568d3c0c4a",
"source_sha256": "70688ba4623298f5ec073331c06c28ca5b0a9c552578e22ccebe20c27d1ca8cf",
"approval_evidence": "26/09/2026 — Aprovação expressa do proprietário nesta sessão: inserir a informação do documento upgrade do portal de investimentos v4 programacao sobre clusters, corredores e projectos ancora nas tabelas e criar os caminhos para a sua execução; eu e que aprovo e estou a aprovar.",
"approval_sha256": "0cbc4b7a3f01dfb6c28659a11597f7e10f87f8c3119bf44ea92b97bec033f3eb",
"original_published_at": "2026-09-26 16:22:52+00"
},
{
"code": "CLU-03",
"title": "Logística, corredores e exportação",
"source_record_id": "ec9494a2-22cc-423e-8a9e-efa1e05ac2b4",
"target_record_id": "99bf7c4c-e23b-47d5-8490-53082384043f",
"expected_target_version": 1,
"content_sha256": "99af7bb35b6769d84bee2175427b3a03027fea65a91dd894338fd7aa72db3820",
"source_sha256": "70688ba4623298f5ec073331c06c28ca5b0a9c552578e22ccebe20c27d1ca8cf",
"approval_evidence": "26/09/2026 — Aprovação expressa do proprietário nesta sessão: inserir a informação do documento upgrade do portal de investimentos v4 programacao sobre clusters, corredores e projectos ancora nas tabelas e criar os caminhos para a sua execução; eu e que aprovo e estou a aprovar.",
"approval_sha256": "0cbc4b7a3f01dfb6c28659a11597f7e10f87f8c3119bf44ea92b97bec033f3eb",
"original_published_at": "2026-09-26 16:22:52+00"
},
{
"code": "CLU-04",
"title": "Turismo costeiro e economia azul",
"source_record_id": "5f0ff662-dc74-41de-adcd-de784a595321",
"target_record_id": "816b7c26-827b-475a-bf98-647b46afe88b",
"expected_target_version": 1,
"content_sha256": "a75f532e2464dadd13dd2ff815d99acfb2f05d3a0264fcf82dfe8e3db7db3a1f",
"source_sha256": "70688ba4623298f5ec073331c06c28ca5b0a9c552578e22ccebe20c27d1ca8cf",
"approval_evidence": "26/09/2026 — Aprovação expressa do proprietário nesta sessão: inserir a informação do documento upgrade do portal de investimentos v4 programacao sobre clusters, corredores e projectos ancora nas tabelas e criar os caminhos para a sua execução; eu e que aprovo e estou a aprovar.",
"approval_sha256": "0cbc4b7a3f01dfb6c28659a11597f7e10f87f8c3119bf44ea92b97bec033f3eb",
"original_published_at": "2026-09-26 16:22:52+00"
},
{
"code": "CLU-05",
"title": "Têxtil, vestuário e algodão",
"source_record_id": "b9b1dc01-dca1-42d8-a9aa-40148dccfaf7",
"target_record_id": "8eb7da82-1f1e-442b-9b1e-57b948c3f057",
"expected_target_version": 1,
"content_sha256": "fe2f37be8e3f20ccc35da91aa86872b960e9820f2e0de84200c157072b3c7b11",
"source_sha256": "70688ba4623298f5ec073331c06c28ca5b0a9c552578e22ccebe20c27d1ca8cf",
"approval_evidence": "26/09/2026 — Aprovação expressa do proprietário nesta sessão: inserir a informação do documento upgrade do portal de investimentos v4 programacao sobre clusters, corredores e projectos ancora nas tabelas e criar os caminhos para a sua execução; eu e que aprovo e estou a aprovar.",
"approval_sha256": "0cbc4b7a3f01dfb6c28659a11597f7e10f87f8c3119bf44ea92b97bec033f3eb",
"original_published_at": "2026-09-26 16:22:52+00"
},
{
"code": "CLU-06",
"title": "Mineração e transformação mineral",
"source_record_id": "0f29da56-b74a-4a69-afaa-b9a0d349580c",
"target_record_id": "65d709f5-20ed-4beb-8cf1-4a7fbd459e8f",
"expected_target_version": 1,
"content_sha256": "097498df28f63b614914cc171e4d58c6154442d0d6625185b3759878829528de",
"source_sha256": "70688ba4623298f5ec073331c06c28ca5b0a9c552578e22ccebe20c27d1ca8cf",
"approval_evidence": "26/09/2026 — Aprovação expressa do proprietário nesta sessão: inserir a informação do documento upgrade do portal de investimentos v4 programacao sobre clusters, corredores e projectos ancora nas tabelas e criar os caminhos para a sua execução; eu e que aprovo e estou a aprovar.",
"approval_sha256": "0cbc4b7a3f01dfb6c28659a11597f7e10f87f8c3119bf44ea92b97bec033f3eb",
"original_published_at": "2026-09-26 16:22:52+00"
},
{
"code": "CLU-07",
"title": "Tecnologia e serviços digitais",
"source_record_id": "523d2aa1-4c7d-4a84-97fc-220f4e845fd7",
"target_record_id": "a795cde3-3cfe-456b-ba8d-7ff3730024ec",
"expected_target_version": 1,
"content_sha256": "6c5407281d18069e05b84e1e7997d07fd79680e2b0e2a5b0e7d86cbfc3c2ca35",
"source_sha256": "70688ba4623298f5ec073331c06c28ca5b0a9c552578e22ccebe20c27d1ca8cf",
"approval_evidence": "26/09/2026 — Aprovação expressa do proprietário nesta sessão: inserir a informação do documento upgrade do portal de investimentos v4 programacao sobre clusters, corredores e projectos ancora nas tabelas e criar os caminhos para a sua execução; eu e que aprovo e estou a aprovar.",
"approval_sha256": "0cbc4b7a3f01dfb6c28659a11597f7e10f87f8c3119bf44ea92b97bec033f3eb",
"original_published_at": "2026-09-26 16:22:52+00"
},
{
"code": "CLU-08",
"title": "Financiamento do investimento",
"source_record_id": "ed2ea4e4-fca7-4cc8-bb8c-f99a1dfda73e",
"target_record_id": "05dc87a8-e1ab-41c9-a312-5b14b8ff16ce",
"expected_target_version": 1,
"content_sha256": "f59505944a91a849c27c16455001ad4a1d39aa58e59e8d5403cb0dc197c2faf8",
"source_sha256": "70688ba4623298f5ec073331c06c28ca5b0a9c552578e22ccebe20c27d1ca8cf",
"approval_evidence": "26/09/2026 — Aprovação expressa do proprietário nesta sessão: inserir a informação do documento upgrade do portal de investimentos v4 programacao sobre clusters, corredores e projectos ancora nas tabelas e criar os caminhos para a sua execução; eu e que aprovo e estou a aprovar.",
"approval_sha256": "0cbc4b7a3f01dfb6c28659a11597f7e10f87f8c3119bf44ea92b97bec033f3eb",
"original_published_at": "2026-09-26 16:22:52+00"
},
{
"code": "COR-01",
"title": "Corredor de Nacala e Norte",
"source_record_id": "dc8f048a-c0bd-476e-9ff2-460dfb12eddc",
"target_record_id": "986af8c5-2f90-40ab-a62a-4c37a5f42e30",
"expected_target_version": 1,
"content_sha256": "26db6621cae0e76d2871f56926062bc0be5bb41e21521d2536655e809b6dff20",
"source_sha256": "09a6a7664f6a910dbc22190ab85d832f5405dd3eaf19e712f313c4241b873b55",
"approval_evidence": "26/09/2026 — Aprovação expressa do proprietário nesta sessão: inserir a informação do documento upgrade do portal de investimentos v4 programacao sobre clusters, corredores e projectos ancora nas tabelas e criar os caminhos para a sua execução; eu e que aprovo e estou a aprovar.",
"approval_sha256": "0cbc4b7a3f01dfb6c28659a11597f7e10f87f8c3119bf44ea92b97bec033f3eb",
"original_published_at": "2026-09-26 16:22:52+00"
},
{
"code": "COR-02",
"title": "Corredor da Beira e Centro",
"source_record_id": "fd5e7b2d-cb48-49e4-bb96-1520263144f2",
"target_record_id": "a7271336-808b-42b4-8bfd-5b49c72dbe2b",
"expected_target_version": 1,
"content_sha256": "0e6b80849fcb0746aa1a53133ee0d5ccc3df8c847243a1134922247ce3d9e1b8",
"source_sha256": "09a6a7664f6a910dbc22190ab85d832f5405dd3eaf19e712f313c4241b873b55",
"approval_evidence": "26/09/2026 — Aprovação expressa do proprietário nesta sessão: inserir a informação do documento upgrade do portal de investimentos v4 programacao sobre clusters, corredores e projectos ancora nas tabelas e criar os caminhos para a sua execução; eu e que aprovo e estou a aprovar.",
"approval_sha256": "0cbc4b7a3f01dfb6c28659a11597f7e10f87f8c3119bf44ea92b97bec033f3eb",
"original_published_at": "2026-09-26 16:22:52+00"
},
{
"code": "COR-03",
"title": "Corredor Zambeze–Macuse",
"source_record_id": "18f3a86c-af85-4fbb-b045-9c3e03c4a610",
"target_record_id": "d9ccddb5-e6aa-4347-a481-37b694013ac1",
"expected_target_version": 1,
"content_sha256": "64c8eba636661e6ebb56e2dd8dd6e978c405dd96c0c7b46bdd62c805018a6c4a",
"source_sha256": "09a6a7664f6a910dbc22190ab85d832f5405dd3eaf19e712f313c4241b873b55",
"approval_evidence": "26/09/2026 — Aprovação expressa do proprietário nesta sessão: inserir a informação do documento upgrade do portal de investimentos v4 programacao sobre clusters, corredores e projectos ancora nas tabelas e criar os caminhos para a sua execução; eu e que aprovo e estou a aprovar.",
"approval_sha256": "0cbc4b7a3f01dfb6c28659a11597f7e10f87f8c3119bf44ea92b97bec033f3eb",
"original_published_at": "2026-09-26 16:22:52+00"
},
{
"code": "COR-04",
"title": "Corredor Maputo–Limpopo",
"source_record_id": "36d72f82-ff0a-468c-89bf-5dc9ae12eddf",
"target_record_id": "ae26d529-5341-4b37-ad08-662c9d09a0cf",
"expected_target_version": 1,
"content_sha256": "ce5b89974e1d2937e4901ab5603245cf2465afbe0a44a224c43f726069cdabd8",
"source_sha256": "09a6a7664f6a910dbc22190ab85d832f5405dd3eaf19e712f313c4241b873b55",
"approval_evidence": "26/09/2026 — Aprovação expressa do proprietário nesta sessão: inserir a informação do documento upgrade do portal de investimentos v4 programacao sobre clusters, corredores e projectos ancora nas tabelas e criar os caminhos para a sua execução; eu e que aprovo e estou a aprovar.",
"approval_sha256": "0cbc4b7a3f01dfb6c28659a11597f7e10f87f8c3119bf44ea92b97bec033f3eb",
"original_published_at": "2026-09-26 16:22:52+00"
},
{
"code": "COR-05",
"title": "Corredor Turístico Inhambane–Bazaruto",
"source_record_id": "06b2d5d7-98ad-48db-b732-8a70b2f5eb4c",
"target_record_id": "69767dcc-4c22-4046-8f81-3797bc44572e",
"expected_target_version": 1,
"content_sha256": "343ba56efeef5514f531cf61126596a8c65f882656da5f95390eb661a0810a7c",
"source_sha256": "09a6a7664f6a910dbc22190ab85d832f5405dd3eaf19e712f313c4241b873b55",
"approval_evidence": "26/09/2026 — Aprovação expressa do proprietário nesta sessão: inserir a informação do documento upgrade do portal de investimentos v4 programacao sobre clusters, corredores e projectos ancora nas tabelas e criar os caminhos para a sua execução; eu e que aprovo e estou a aprovar.",
"approval_sha256": "0cbc4b7a3f01dfb6c28659a11597f7e10f87f8c3119bf44ea92b97bec033f3eb",
"original_published_at": "2026-09-26 16:22:52+00"
},
{
"code": "COR-06",
"title": "Corredor Industrial Nacional Norte–Sul",
"source_record_id": "6727e46b-98e1-47a7-be04-dfdb1c6375cb",
"target_record_id": "fb85ebfc-798c-42df-a267-d4b771c443f8",
"expected_target_version": 1,
"content_sha256": "465ff9717def3bad42efb25d1ffa04812f871e8b167adcbb20f68f5ef9460b57",
"source_sha256": "09a6a7664f6a910dbc22190ab85d832f5405dd3eaf19e712f313c4241b873b55",
"approval_evidence": "26/09/2026 — Aprovação expressa do proprietário nesta sessão: inserir a informação do documento upgrade do portal de investimentos v4 programacao sobre clusters, corredores e projectos ancora nas tabelas e criar os caminhos para a sua execução; eu e que aprovo e estou a aprovar.",
"approval_sha256": "0cbc4b7a3f01dfb6c28659a11597f7e10f87f8c3119bf44ea92b97bec033f3eb",
"original_published_at": "2026-09-26 16:22:52+00"
}
]
}
@@ -0,0 +1,31 @@
{
"target": "https://teste-investidor.tempo.co.mz",
"authorization": "Tempo, 23/10/2026: concluir visual QA, integrar pelo Git e conferir apenas os tres deltas instalados",
"status": "installed",
"commit": "6b544548d3c83fc8a72ab65d8f58f84685a2877f",
"finished_at": "2026-10-23T13:45:19.950347+00:00",
"manifest_sha256": "65a694b5d81f833446a308f48f4966b32a8bdc5421f1ee2196b79ac0b798b636",
"release": "/var/www/investmoz-homologacao/releases/6b544548d3c83fc8a72ab65d8f58f84685a2877f-b38d94da",
"previous_release": "/var/www/investmoz-homologacao/releases/7c1f532134e4492dec010d0c511f669a1e2c23a7-524bd081",
"git": "Fast-forward from 7c1f5321; no remote-exclusive main changes; no force push",
"database_migrations": 0,
"content_imports": 0,
"permission_changes": 0,
"automatic_frontend_tests": {"tests": 206, "passed": 206, "failed": 0},
"qa_visual_completed": "Beira page displays seven documented opportunities; Florestas opens and both preparation links retain from_dossier",
"destination_delta": {
"language": "Existing private TESTE DE HOMOLOGACAO dossier: Portuguese (Mozambique) selected, explicit save confirmed, page reload recovered the same language without false conflict",
"test_dossier": "958df138-7980-4685-8389-fa90be02ed6b",
"simulation_draft": "6c07a968-7ff9-42fa-b252-072bb1acf221",
"report": "Printed report revision 4 distinguishes title from user dossier data, summary inherited from Florestas opportunity version 2, sector from sector catalogue and Sofala from territorial catalogue",
"beira": "Seven detail pages opened from observed links; exact titles and from_dossier preserved in preparation links",
"beira_titles": ["Açúcar — Corredor da Beira", "Carne bovina — Corredor da Beira", "Florestas — Corredor da Beira", "Hortícolas — Corredor da Beira", "Milho, trigo e arroz — Corredor da Beira", "Sésamo — Corredor da Beira", "Soja — Corredor da Beira"],
"stale_versions": "Accepted QA rejection tests reused; installed server guard bytes match the tested version. No deliberate stale write against destination user data",
"version_guard_sha256": "1626280b34481298de964ff82027ae7ea9b1aa80d1b86554987edbe6ec5a087f",
"draft_client_sha256": "dfe1a44e8a9af48735d75d0742583ce2280abf7615049d569f7963763bdd5e4c"
},
"local_proofs": ["outputs/preservation-inventory-20261023/beira-seven-visual-QA.png", "outputs/preservation-inventory-20261023/installed-language-recovery-6b544548.png", "outputs/preservation-inventory-20261023/installed-provenance-report-6b544548.png", "outputs/preservation-inventory-20261023/installed-beira-seven-6b544548.png"],
"preservation": "No republication of the 34 opportunities or reimport of portfolio; only existing labelled private test dossier used for writes; previous release preserved",
"rollback": "Existing release rollback; retain database, uploads, permissions, history and later edits",
"remaining": "Transversal application remains partial; this receipt closes only the three installed correction deltas"
}
@@ -0,0 +1,25 @@
{
"decision_owner": "Tempo, Lda.",
"decision_date": "2026-10-23",
"configuration": "config/investment_corridors.php",
"functional_revision": "0025309b",
"maputo_final_decision": ["Maputo Província", "Maputo Cidade"],
"automatic_province": {"nacala": "Nampula", "limpopo": "Gaza"},
"existing_territorial_ids_reused": true,
"server_validation": ["corridor and territory", "documented zone compatibility", "actor and dossier access", "draft request ordering", "dossier concurrency"],
"recovery": "Encrypted pending draft in the existing dossier payload, per actor; distinct from confirmed context.",
"qa": {
"database": "simulator_fiscal_delta_20261021_test",
"port": 55441,
"focused_tests": 5,
"assertions": 90,
"existing_opportunity_continuity_tests": "passed",
"types": "passed",
"build": "passed",
"browser": ["Beira offers Sofala and Manica", "Nacala selects Nampula", "Maputo offers both units", "Sofala/Dondo recovers after reload", "normal context confirmation succeeds"],
"browser_account_and_dossier": "synthetic and private, QA only",
"screenshot": "outputs/preservation-inventory-20261023/corridor-adaptive-territory-QA.png"
},
"homologation": {"git_integrated": true, "public_read_check": "Maputo province endpoint returns exactly the two existing units", "real_dossier_test_writes": false},
"installation": {"migrations": 0, "imports": 0, "republication": false, "new_roles": false, "geometry_changes": false, "automatic_fiscal_calculation": false}
}
@@ -0,0 +1,657 @@
{
"scope": "homologation private registration, not publication or editorial acceptance",
"functional_revision": "65eddf6bc7025c81cbd7b0c5918b6ae8bdb1c1f6",
"candidate": "fb4e7d897083957486385aa517969afd1a10b417",
"checked_utc": "2026-10-22T21:54:25+00:00",
"checked_Africa_Johannesburg": "2026-10-22T23:54:25+02:00",
"management_url": "https://teste-investidor.tempo.co.mz/gestao-conteudos",
"catalogue_url": "https://teste-investidor.tempo.co.mz/management/portal-services?state=draft",
"actor_id": 15,
"account": "Osmane Nala",
"real_password_MFA_session": true,
"MFA_fabricated_or_extended": false,
"mechanism": "Existing authenticated HTTPS upload, metadata and editorial endpoints using the transported server originals; no direct SQL data writes",
"archive_retransported": false,
"archive_sha256": "177c50d1cc3b1610e62123a2f8b532711a15f875aac2ee0e372636c2f747e8a2",
"backup_restore": {
"restored_tables": 174,
"files_verified": 341,
"dump_sha256": "8658b5fa0d1ef16c9fc9be90969badde6007443aa474fe4a9377f16d80fe6e92",
"files_archive_sha256": "1d98b6200ffae11bb07dc600daa6416b3b1b90735de42791858a07a0caa63552"
},
"preservation": {
"target": {
"name": "teste_investidor",
"port": 5432
},
"existing_rows_preserved": {
"documents": 32,
"portal_resources": 31,
"portal_resource_revisions": 63,
"portal_resource_links": 19,
"users": 17,
"geo_locations": 1921,
"geo_geometries": 408
},
"grants_preserved": {
"portal_role_assignments": true,
"portal_superuser_pair": true,
"portal_security_approvals": true
},
"new_private_articles": 28,
"new_publications": 0,
"new_originals": 28,
"clean_scans_matching_bytes": 28,
"original_sha256_unchanged": true,
"checked_utc": "2026-10-22T21:54:25+00:00"
},
"rows": [
{
"external_id": "legacy-tempo-post-62413-20170626",
"legacy_id": 62413,
"title": "15/30 costa do sol com dua na zona de mupulene muito lindo",
"source_url": "https://investemoz.tempo.co.mz/1530-costa-do-sol-com-dua-na-zona-de-mupulene-muito-lindo",
"resource_id": 32,
"document_id": "c20a1729-0a93-4b9e-beab-13b1320f6587",
"sha256": "b471c1afd7235f359acdcc16a93088f77e1f988706c8d3c4d4f11c5d560aea47",
"scan": "clean",
"scanned_sha256_matches": true,
"private": true,
"metadata_version": 3,
"search_http": 200,
"edit_http": 200,
"preview_http": 200,
"anonymous_article_http": 404,
"image_href": "/documentos/27/view",
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
"body_image_references": 1,
"edition_recovered": true
},
{
"external_id": "legacy-tempo-post-73945-20170731",
"legacy_id": 73945,
"title": "Banco Mundial classifica de proibitivos juros praticados em Moçambique",
"source_url": "https://investemoz.tempo.co.mz/banco-mundial-classifica-de-proibitivos-juros-praticados-em-mocambique",
"resource_id": 33,
"document_id": "5c550542-79c4-4b86-bb12-3db3e8daa631",
"sha256": "2aa0ce0eb305bfdf93e390a978d3694d0fbc0129034077b68cebce418f878415",
"scan": "clean",
"scanned_sha256_matches": true,
"private": true,
"metadata_version": 1,
"search_http": 200,
"edit_http": 200,
"preview_http": 200,
"anonymous_article_http": 404,
"image_href": "/documentos/27/view",
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
"body_image_references": 1,
"edition_recovered": true
},
{
"external_id": "legacy-tempo-post-76474-20170808",
"legacy_id": 76474,
"title": "Coloque o seu número para o grupo de whatsapp, venda de acessórios de computador",
"source_url": "https://investemoz.tempo.co.mz/coloque-o-seu-numero-para-o-grupo-de-whatsapp-venda-de-acessorios-de-computador",
"resource_id": 34,
"document_id": "baff8e03-3327-40a0-af01-3dd11d163871",
"sha256": "8f769aec4715449d056c8c25294060b2ff290ec0893c28e9bdcc4e8092fc75f5",
"scan": "clean",
"scanned_sha256_matches": true,
"private": true,
"metadata_version": 1,
"search_http": 200,
"edit_http": 200,
"preview_http": 200,
"anonymous_article_http": 404,
"image_href": "/documentos/27/view",
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
"body_image_references": 2,
"edition_recovered": true
},
{
"external_id": "legacy-tempo-post-62414-20170626",
"legacy_id": 62414,
"title": "Frelimo e Zanu-PF: Evocados laços de irmandade",
"source_url": "https://investemoz.tempo.co.mz/frelimo-e-zanu-pf-evocados-lacos-de-irmandade",
"resource_id": 35,
"document_id": "a5050bd9-e868-4347-9a24-03c2b6c04ab9",
"sha256": "a040c0b7cb95dd7f053f4d13662dd58b2b06453b6fa61a2d531d0257a8db8c17",
"scan": "clean",
"scanned_sha256_matches": true,
"private": true,
"metadata_version": 1,
"search_http": 200,
"edit_http": 200,
"preview_http": 200,
"anonymous_article_http": 404,
"image_href": "/documentos/27/view",
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
"body_image_references": 1,
"edition_recovered": true
},
{
"external_id": "legacy-tempo-post-62418-20170626",
"legacy_id": 62418,
"title": "Governo dará todo O apoio à PGR no seguimento da auditoria",
"source_url": "https://investemoz.tempo.co.mz/governo-dara-todo-o-apoio-a-pgr-no-seguimento-da-auditoria",
"resource_id": 36,
"document_id": "c3d4031c-62c8-4bbf-ab6c-193412036888",
"sha256": "e8ccafa68f0d1aa80f2e41d9e09f7b13e7c653a8472a731798cfd5e04ce2963e",
"scan": "clean",
"scanned_sha256_matches": true,
"private": true,
"metadata_version": 1,
"search_http": 200,
"edit_http": 200,
"preview_http": 200,
"anonymous_article_http": 404,
"image_href": "/documentos/27/view",
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
"body_image_references": 1,
"edition_recovered": true
},
{
"external_id": "legacy-tempo-post-75002-20170804",
"legacy_id": 75002,
"title": "Governo pretende construir 100 mil habitações em dois anos",
"source_url": "https://investemoz.tempo.co.mz/governo-pretende-construir-100-mil-habitacoes-em-dois-anos",
"resource_id": 37,
"document_id": "97eeceb3-f61e-4980-a884-3228f4707af8",
"sha256": "7b6fbd998be2b47b96e9a4724560c192c502709fa308d6590c0797ddcdb10544",
"scan": "clean",
"scanned_sha256_matches": true,
"private": true,
"metadata_version": 1,
"search_http": 200,
"edit_http": 200,
"preview_http": 200,
"anonymous_article_http": 404,
"image_href": "/documentos/27/view",
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
"body_image_references": 1,
"edition_recovered": true
},
{
"external_id": "legacy-tempo-post-73246-20170728",
"legacy_id": 73246,
"title": "Rofino Licuco preso cinco meses depois de condenado por agressão",
"source_url": "https://investemoz.tempo.co.mz/rofino-licuco-preso-cinco-meses-depois-de-condenado-por-agressao",
"resource_id": 38,
"document_id": "409b9017-d89e-42b3-a7ec-741f2f1f7683",
"sha256": "430ee8674c71104fc126bed2911d676f150ac623857a1ada75662987fd9ac390",
"scan": "clean",
"scanned_sha256_matches": true,
"private": true,
"metadata_version": 1,
"search_http": 200,
"edit_http": 200,
"preview_http": 200,
"anonymous_article_http": 404,
"image_href": "/documentos/27/view",
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
"body_image_references": 1,
"edition_recovered": true
},
{
"external_id": "legacy-tempo-post-77232-20170811",
"legacy_id": 77232,
"title": "Sete indivíduos mortos a tiro na Matola",
"source_url": "https://investemoz.tempo.co.mz/sete-individuos-mortos-a-tiro-na-matola",
"resource_id": 39,
"document_id": "5d0a813f-9001-4d04-9b84-865977c2f1f2",
"sha256": "27544d3127803b7495bef9fd633147d0225d8c31816c4d59ed05a2edd59de070",
"scan": "clean",
"scanned_sha256_matches": true,
"private": true,
"metadata_version": 1,
"search_http": 200,
"edit_http": 200,
"preview_http": 200,
"anonymous_article_http": 404,
"image_href": "/documentos/27/view",
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
"body_image_references": 1,
"edition_recovered": true
},
{
"external_id": "legacy-tempo-post-62416-20170626",
"legacy_id": 62416,
"title": "Sete pessoas morrem e 150 são resgatados em acidente com eléctrico na Índia",
"source_url": "https://investemoz.tempo.co.mz/sete-pessoas-morrem-e-150-sao-resgatados-em-acidente-com-electrico-na-india",
"resource_id": 40,
"document_id": "3856ae7f-be39-4926-b653-cfd67aeaf003",
"sha256": "ac575ce340571192552c89b63f0bd62a84dbc47828e6930133af5ff70e249319",
"scan": "clean",
"scanned_sha256_matches": true,
"private": true,
"metadata_version": 1,
"search_http": 200,
"edit_http": 200,
"preview_http": 200,
"anonymous_article_http": 404,
"image_href": "/documentos/27/view",
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
"body_image_references": 0,
"edition_recovered": true
},
{
"external_id": "legacy-tempo-post-62929-20170627",
"legacy_id": 62929,
"title": "Daúde Razaque já não é treinador da Liga Desportiva de Maputo",
"source_url": "https://investemoz.tempo.co.mz/daude-razaque-ja-nao-e-treinador-da-liga-desportiva-de-maputo",
"resource_id": 41,
"document_id": "8f8b749d-1a2b-4e87-93b9-f89a70bdb6a8",
"sha256": "488017b062c6760659d9147b7f12aea4afd8d824121adb9462e2c7a2ae58437b",
"scan": "clean",
"scanned_sha256_matches": true,
"private": true,
"metadata_version": 1,
"search_http": 200,
"edit_http": 200,
"preview_http": 200,
"anonymous_article_http": 404,
"image_href": "/documentos/27/view",
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
"body_image_references": 1,
"edition_recovered": true
},
{
"external_id": "legacy-tempo-post-62649-20170626",
"legacy_id": 62649,
"title": "Daviz Simango não se incomoda com possível sucessão na Beira",
"source_url": "https://investemoz.tempo.co.mz/daviz-simango-nao-se-incomoda-com-possivel-sucessao-na-beira",
"resource_id": 42,
"document_id": "3060923e-c1c0-4b9c-bafb-15f61d53fcc9",
"sha256": "38a858713472759a2d20a308bdd9d8afa9cfb76dcaf8d11ca709e89261573e42",
"scan": "clean",
"scanned_sha256_matches": true,
"private": true,
"metadata_version": 1,
"search_http": 200,
"edit_http": 200,
"preview_http": 200,
"anonymous_article_http": 404,
"image_href": "/documentos/27/view",
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
"body_image_references": 1,
"edition_recovered": true
},
{
"external_id": "legacy-tempo-post-62739-20170626",
"legacy_id": 62739,
"title": "“Exigimos responsabilização dos agentes do crime”, Daviz Simango",
"source_url": "https://investemoz.tempo.co.mz/exigimos-responsabilizacao-dos-agentes-do-crime-daviz-simango-2",
"resource_id": 43,
"document_id": "674b3cbf-d7ff-4521-8650-b420837dbdd5",
"sha256": "5870fc0fad9bb7af537b7b1468151cde138b1b886ce846c7be25647b630245dd",
"scan": "clean",
"scanned_sha256_matches": true,
"private": true,
"metadata_version": 1,
"search_http": 200,
"edit_http": 200,
"preview_http": 200,
"anonymous_article_http": 404,
"image_href": "/documentos/27/view",
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
"body_image_references": 1,
"edition_recovered": true
},
{
"external_id": "legacy-tempo-post-62854-20170627",
"legacy_id": 62854,
"title": "Governo e parceiros reforçam mecanismos de controlo de HIV/Sida",
"source_url": "https://investemoz.tempo.co.mz/governo-e-parceiros-reforcam-mecanismos-de-controlo-de-hivsida",
"resource_id": 44,
"document_id": "a2469350-35d7-4d70-8574-a46975360016",
"sha256": "44b1ba9d593bb625cf53de8909e78eb0b3129742c3ec9d58ec68c984e8930bce",
"scan": "clean",
"scanned_sha256_matches": true,
"private": true,
"metadata_version": 1,
"search_http": 200,
"edit_http": 200,
"preview_http": 200,
"anonymous_article_http": 404,
"image_href": "/documentos/27/view",
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
"body_image_references": 1,
"edition_recovered": true
},
{
"external_id": "legacy-tempo-post-63128-20170627",
"legacy_id": 63128,
"title": "Graça Machel diz que desnutrição é um problema de desenvolvimento",
"source_url": "https://investemoz.tempo.co.mz/graca-machel-diz-que-desnutricao-e-um-problema-de-desenvolvimento",
"resource_id": 45,
"document_id": "f07797a0-4dcc-4706-acfd-2424b6bd6130",
"sha256": "74be8f29e5053f53ab1a39a2f63208145c30f23357ca6d78b637dd02bf9c1fb4",
"scan": "clean",
"scanned_sha256_matches": true,
"private": true,
"metadata_version": 1,
"search_http": 200,
"edit_http": 200,
"preview_http": 200,
"anonymous_article_http": 404,
"image_href": "/documentos/27/view",
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
"body_image_references": 1,
"edition_recovered": true
},
{
"external_id": "legacy-tempo-post-63181-20170627",
"legacy_id": 63181,
"title": "In dependência",
"source_url": "https://investemoz.tempo.co.mz/in-dependencia",
"resource_id": 46,
"document_id": "34d11ba7-086e-4290-9181-0c6adebc6658",
"sha256": "3d0f9ac14d36156d8178e33f8616d6406f31ea4cd69c8308cbf4227586d7ccb3",
"scan": "clean",
"scanned_sha256_matches": true,
"private": true,
"metadata_version": 1,
"search_http": 200,
"edit_http": 200,
"preview_http": 200,
"anonymous_article_http": 404,
"image_href": "/documentos/27/view",
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
"body_image_references": 1,
"edition_recovered": true
},
{
"external_id": "legacy-tempo-post-63146-20170627",
"legacy_id": 63146,
"title": "Kroll não descobriu como foram gastos 2 biliões de dólares; Governo de Nyusi não apoiou a Auditoria; criação da Proindicus, EMATUM e MAM não foi para proteção da Zona Económica Exclusiva",
"source_url": "https://investemoz.tempo.co.mz/kroll-nao-descobriu-como-foram-gastos-2-bilioes-de-dolares-governo-de-nyusi-nao-apoiou-a-auditoria-criacao-da-proindicus-ematum-e-mam-nao-foi-para-protecao-da-zona-economica-exclusiva-2",
"resource_id": 47,
"document_id": "27d98ca7-b59f-478f-8560-a51db4354069",
"sha256": "542353466cc6d82b651619c495f138b0576a4be41d21aad194ae2005050cb1bc",
"scan": "clean",
"scanned_sha256_matches": true,
"private": true,
"metadata_version": 1,
"search_http": 200,
"edit_http": 200,
"preview_http": 200,
"anonymous_article_http": 404,
"image_href": "/documentos/27/view",
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
"body_image_references": 1,
"edition_recovered": true
},
{
"external_id": "legacy-tempo-post-62688-20170626",
"legacy_id": 62688,
"title": "O Instituto Nacional de Meteorologia prevê o seguinte estado do tempo para esta ...",
"source_url": "https://investemoz.tempo.co.mz/o-instituto-nacional-de-meteorologia-preve-o-seguinte-estado-do-tempo-para-esta-29",
"resource_id": 48,
"document_id": "200a6292-c01b-4c83-bf3c-959379676651",
"sha256": "a210a789b06d425822f4124f74226a2b27246c086c5ec5f041f1b2237d7c2b65",
"scan": "clean",
"scanned_sha256_matches": true,
"private": true,
"metadata_version": 1,
"search_http": 200,
"edit_http": 200,
"preview_http": 200,
"anonymous_article_http": 404,
"image_href": "/documentos/27/view",
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
"body_image_references": 0,
"edition_recovered": true
},
{
"external_id": "legacy-tempo-post-63248-20170628",
"legacy_id": 63248,
"title": "Dois alunos da 9ª classe caíram nas mãos da Polícia da República de Moçambique (...",
"source_url": "https://investemoz.tempo.co.mz/dois-alunos-da-9a-classe-cairam-nas-maos-da-policia-da-republica-de-mocambique",
"resource_id": 49,
"document_id": "fc19fc89-4589-48b3-8328-9398ad367650",
"sha256": "6c30e61197b8568332af6d9b19bbba7ad8665b3873bb031a8c8aedd41536b060",
"scan": "clean",
"scanned_sha256_matches": true,
"private": true,
"metadata_version": 1,
"search_http": 200,
"edit_http": 200,
"preview_http": 200,
"anonymous_article_http": 404,
"image_href": "/documentos/27/view",
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
"body_image_references": 0,
"edition_recovered": true
},
{
"external_id": "legacy-tempo-post-173220-20260616",
"legacy_id": 173220,
"title": "Alumínio “Verde”",
"source_url": "https://investemoz.tempo.co.mz/aluminio-verde",
"resource_id": 50,
"document_id": "9b3b5f7e-904e-4ac0-a389-b93fbc4f5ef6",
"sha256": "5b76c3d84dcf7f9a489686d90e3857ae6536705c918e841310bb42b354e80976",
"scan": "clean",
"scanned_sha256_matches": true,
"private": true,
"metadata_version": 1,
"search_http": 200,
"edit_http": 200,
"preview_http": 200,
"anonymous_article_http": 404,
"image_href": "/documentos/27/view",
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
"body_image_references": 0,
"edition_recovered": true
},
{
"external_id": "legacy-tempo-post-173222-20260616",
"legacy_id": 173222,
"title": "Energia e Recursos Naturais",
"source_url": "https://investemoz.tempo.co.mz/energia-recursos-naturais",
"resource_id": 51,
"document_id": "7a4a544f-8159-47e7-b69c-b7a83f8324b1",
"sha256": "e64d7063d1ac4dda028c97e1ba64938d2a9e426a0b630c57ee5df52a41312cc7",
"scan": "clean",
"scanned_sha256_matches": true,
"private": true,
"metadata_version": 1,
"search_http": 200,
"edit_http": 200,
"preview_http": 200,
"anonymous_article_http": 404,
"image_href": "/documentos/27/view",
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
"body_image_references": 0,
"edition_recovered": true
},
{
"external_id": "legacy-tempo-post-173482-20260915",
"legacy_id": 173482,
"title": "Checklist para Contratação e Direitos do Trabalho Doméstico",
"source_url": "https://investemoz.tempo.co.mz/checklist-para-contratacao-e-direitos-do-trabalho-domestico",
"resource_id": 52,
"document_id": "bbe4154b-c1d1-4ab3-8a36-92c4764df0a3",
"sha256": "6f888e133a779c719073246f89bcae150198e5897c0e7d9b7ba75f54c54a7830",
"scan": "clean",
"scanned_sha256_matches": true,
"private": true,
"metadata_version": 1,
"search_http": 200,
"edit_http": 200,
"preview_http": 200,
"anonymous_article_http": 404,
"image_href": "/documentos/27/view",
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
"body_image_references": 0,
"edition_recovered": true
},
{
"external_id": "legacy-tempo-post-173484-20260915",
"legacy_id": 173484,
"title": "Direitos Especiais da Trabalhadora Doméstica no Regulamento",
"source_url": "https://investemoz.tempo.co.mz/direitos-especiais-da-trabalhadora-domestica-no-regulamento",
"resource_id": 53,
"document_id": "712ed069-72de-4cab-bc81-6d589aa22cb9",
"sha256": "297df455a9c6fd1f0ab7ca7bbe124ce483c86fe55be26baa695f03089b3ac049",
"scan": "clean",
"scanned_sha256_matches": true,
"private": true,
"metadata_version": 1,
"search_http": 200,
"edit_http": 200,
"preview_http": 200,
"anonymous_article_http": 404,
"image_href": "/documentos/27/view",
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
"body_image_references": 0,
"edition_recovered": true
},
{
"external_id": "legacy-tempo-post-173478-20260915",
"legacy_id": 173478,
"title": "Guia Prático para Empregadores sobre Contratação e Direitos no Trabalho Doméstico",
"source_url": "https://investemoz.tempo.co.mz/guia-pratico-para-empregadores-sobre-contratacao-e-direitos-no-trabalho-domestico",
"resource_id": 54,
"document_id": "ba35f2e7-3d75-4dfc-b2e8-3e18425419ec",
"sha256": "d4e320ed99dd9d53d205e47932f73651976d43081d5d39f2bfc2996901408644",
"scan": "clean",
"scanned_sha256_matches": true,
"private": true,
"metadata_version": 1,
"search_http": 200,
"edit_http": 200,
"preview_http": 200,
"anonymous_article_http": 404,
"image_href": "/documentos/27/view",
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
"body_image_references": 0,
"edition_recovered": true
},
{
"external_id": "legacy-tempo-post-173480-20260915",
"legacy_id": 173480,
"title": "Modelo de Contrato de Trabalho Doméstico para Pequenas Famílias",
"source_url": "https://investemoz.tempo.co.mz/modelo-de-contrato-de-trabalho-domestico-para-pequenas-familias",
"resource_id": 55,
"document_id": "e48a87d1-36fd-4e03-aece-a5184e390eff",
"sha256": "c3ee6401eda5b582c61969b5e3b74fdf26ef34ca481b1aca8c4c309d845147ae",
"scan": "clean",
"scanned_sha256_matches": true,
"private": true,
"metadata_version": 1,
"search_http": 200,
"edit_http": 200,
"preview_http": 200,
"anonymous_article_http": 404,
"image_href": "/documentos/27/view",
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
"body_image_references": 0,
"edition_recovered": true
},
{
"external_id": "legacy-tempo-post-173476-20260915",
"legacy_id": 173476,
"title": "Procedimentos para Rescisão do Contrato de Trabalho Doméstico segundo o Regulamento de Moçambique",
"source_url": "https://investemoz.tempo.co.mz/procedimentos-para-rescisao-do-contrato-de-trabalho-domestico-segundo-o-regulamento-de-mocambique",
"resource_id": 56,
"document_id": "57dd9068-c5c1-406a-bbc1-d0fdb79f038e",
"sha256": "72fbf28391db4804e0637b64e7f59e9fa597812a2681b4562dc3d6dfb4093a30",
"scan": "clean",
"scanned_sha256_matches": true,
"private": true,
"metadata_version": 1,
"search_http": 200,
"edit_http": 200,
"preview_http": 200,
"anonymous_article_http": 404,
"image_href": "/documentos/27/view",
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
"body_image_references": 0,
"edition_recovered": true
},
{
"external_id": "legacy-tempo-post-173470-20260915",
"legacy_id": 173470,
"title": "Direitos e Deveres do Trabalhador Doméstico",
"source_url": "https://investemoz.tempo.co.mz/direitos-e-deveres-do-trabalhador-domestico",
"resource_id": 57,
"document_id": "de254617-49c2-407d-9e3c-e466488dd46b",
"sha256": "26feeae6b8d04972c7a64ee360583cccdcaeb4b0d8795fd7bc2fd3c38866cc19",
"scan": "clean",
"scanned_sha256_matches": true,
"private": true,
"metadata_version": 1,
"search_http": 200,
"edit_http": 200,
"preview_http": 200,
"anonymous_article_http": 404,
"image_href": "/documentos/27/view",
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
"body_image_references": 0,
"edition_recovered": true
},
{
"external_id": "legacy-tempo-post-173474-20260915",
"legacy_id": 173474,
"title": "Sanções Disciplinares Aplicáveis a Trabalhadores Domésticos segundo o Regulamento de Moçambique",
"source_url": "https://investemoz.tempo.co.mz/sancoes-disciplinares-aplicaveis-a-trabalhadores-domesticos-segundo-o-regulamento-de-mocambique",
"resource_id": 58,
"document_id": "17e4a9cb-286a-4c87-a5bc-9f1e91a5da44",
"sha256": "9fa741a17b746bfa2f3ce7b6ce1d2bb34d3a35d3b58330b0554f9e0a6844f049",
"scan": "clean",
"scanned_sha256_matches": true,
"private": true,
"metadata_version": 1,
"search_http": 200,
"edit_http": 200,
"preview_http": 200,
"anonymous_article_http": 404,
"image_href": "/documentos/27/view",
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
"body_image_references": 0,
"edition_recovered": true
},
{
"external_id": "legacy-tempo-post-173463-20260915",
"legacy_id": 173463,
"title": "Regulamento do Trabalho Doméstico",
"source_url": "https://investemoz.tempo.co.mz/regulamento-do-trabalho-domestico",
"resource_id": 59,
"document_id": "20ce1dd9-729f-458f-b650-f5eb55c56d60",
"sha256": "4f34b7bac4b3030c0614844d379f6e3cfad1b7f6ea3db575a58c33ae033b0e73",
"scan": "clean",
"scanned_sha256_matches": true,
"private": true,
"metadata_version": 1,
"search_http": 200,
"edit_http": 200,
"preview_http": 200,
"anonymous_article_http": 404,
"image_href": "/documentos/27/view",
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
"body_image_references": 0,
"edition_recovered": true
}
],
"UI_demonstration": {
"management_search": "15/30",
"resource_id": 32,
"save_and_recover_version": 3,
"body_changed_in_destination_UI": false,
"private_preview": true,
"qa_note_preserved_for_team_review": true
},
"public20_HTTP_repeated": false,
"new_publications": 0,
"QA_tests_repeated": false,
"new_roles_or_approvers": false,
"unresolved_originals": 2,
"inline_image_references_preserved": 16,
"existing_original_image_reused_by_exact_hash": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa",
"anchor_projects_and_empty_portfolio": "Separate failure; cause remains unconfirmed; not resolved by this delivery",
"user_review_of_commit_package_or_guide_claimed": false,
"private_delivery_executor_sha256": "308ac6e0c5deb99e4176af5891e8e084aa327864b460d5d523931c6e2379691c",
"private_verifier_sha256": "a669324009315e44271d5d536a08a4049e9e2ba68d1a3f52f3a0921e5a3a0fb1"
}
@@ -0,0 +1,949 @@
{
"date": "2026-10-23",
"current_public_readonly_confirmation": {
"date": "2026-10-23",
"url": "https://teste-investidor.tempo.co.mz/carteira",
"visible_dossiers": 29,
"projects": 15,
"clusters": 8,
"corridors": 6,
"manifest": "approved-portfolio-visibility-20261023.json",
"missing_manifest_ids": [],
"different_titles": [],
"session_scope": "Authenticated browser reading the public route; prior anonymous recovery proof preserved",
"remote_data_writes": 0,
"remaining_continuity_gap": "from_dossier is validated on opportunity consultation but not carried by the Prepare investment link into the private context"
},
"operation": "Inventory before further acceptance tests; no active data changed",
"baseline_commit": "37a8eb015ac5022fd34bcd89cfe47c9fc61903b0",
"existing_43_components": [
{
"number": 1,
"component": "Documentos públicos",
"historic_status": "Instalado mas desabilitado",
"current_preservation_status": "Disponível: 29 recursos públicos; recibo editorial 22/10. Os 20 artigos conservam a aceitação anterior.",
"evidence": "[Abrir](https://teste-investidor.tempo.co.mz/documentos) · 29 fichas privadas; zero publicação adicional. Consulta pública do acervo permanece fechada."
},
{
"number": 2,
"component": "Ficha integral dos parques",
"historic_status": "Instalado mas desabilitado",
"current_preservation_status": "Disponibilizado localmente em 18/10; destino conserva 16 fichas, 8 publicadas. Recorte integral não é reaprovado por este inventário.",
"evidence": "[Abrir](https://teste-investidor.tempo.co.mz/zonas) · 16 fichas privadas/pendentes, 19 relações territoriais; publicação e gestão autorizada separadas."
},
{
"number": 3,
"component": "Navegação contextual e manifestação de interesse",
"historic_status": "Disponível",
"current_preservation_status": "Navegação/percurso preservados; carteira documental reposta em 23/10: 29 dossiers, incluindo 15 projectos âncora.",
"evidence": "[Abrir](https://teste-investidor.tempo.co.mz/investimentos/iniciar) · Início, contexto e navegação abrem; gravação/recuperação aceites em QA reutilizadas. Sem dossier real no destino."
},
{
"number": 4,
"component": "Referências económicas — lote mínimo",
"historic_status": "Instalado mas desabilitado",
"current_preservation_status": "Código e referências presentes; manter o âmbito da autorização anterior, sem inferir publicação de todas as opções.",
"evidence": "[Abrir](https://teste-investidor.tempo.co.mz/management/investment/references) · 11 actividades presentes; cinco novas referências transportadas. Activação/publicação do lote não executada."
},
{
"number": 5,
"component": "Sectores e duplicação visual",
"historic_status": "Disponível",
"current_preservation_status": "Entrega de 18/10 conservada no código/histórico; sem remoção constatada nesta leitura.",
"evidence": "[Abrir](https://teste-investidor.tempo.co.mz/sectores) · Ecrã aberto: dez sectores principais, especialização Fintech sem cartão principal duplicado; 12 registos conservados."
},
{
"number": 6,
"component": "Duplicados distintos — deteção e consolidação",
"historic_status": "Não demonstrado",
"current_preservation_status": "Sem desaparecimento demonstrado; conserva o âmbito parcial/não demonstrado anterior, sem promover conclusão nem reabrir provas aceites.",
"evidence": "[Abrir](https://teste-investidor.tempo.co.mz/management/sector-reconciliation) · Código presente; consolidação de registos distintos não demonstrada no destino."
},
{
"number": 7,
"component": "Fintech — enquadramento específico",
"historic_status": "Instalado mas desabilitado",
"current_preservation_status": "Fintech público no lote aceite; especialização preservada. Não reabrir a organização sectorial aprovada.",
"evidence": "[Abrir](https://teste-investidor.tempo.co.mz/sectores/mercado-financeiro/fintech) · Código instalado; artigo de origem permanece privado, sem publicação adicional."
},
{
"number": 8,
"component": "Legado — conteúdo restante",
"historic_status": "Instalado mas desabilitado",
"current_preservation_status": "20 artigos públicos e 28 privados cadastrados no destino; dois originais incompletos. Recibo 37a8eb01.",
"evidence": "[Abrir](https://teste-investidor.tempo.co.mz/sectores) · Adaptador habilitado; artigos/imagens transportados privadamente, sem os publicar."
},
{
"number": 9,
"component": "Piloto agronegócio",
"historic_status": "Instalado mas desabilitado",
"current_preservation_status": "Piloto Agronegócio concluído: artigo e imagem preservados; não é uma oportunidade empresarial concreta.",
"evidence": "[Abrir](https://teste-investidor.tempo.co.mz/sectores) · Originais privados; seis oportunidades sintéticas excluídas. Nenhuma oportunidade real publicada."
},
{
"number": 10,
"component": "Percurso do investidor — 19 etapas",
"historic_status": "Disponível",
"current_preservation_status": "Percurso e preparação privada habilitados no destino; entrega adaptativa de 22/10. Zero dossiers pessoais não é perda de função.",
"evidence": "[Abrir](https://teste-investidor.tempo.co.mz/investimentos) · Início e lista própria abrem; definição de 19 etapas instalada. Não repetidas as 19 etapas aceites."
},
{
"number": 11,
"component": "Fiscal/contributivo e exportações",
"historic_status": "Não demonstrado",
"current_preservation_status": "Tesouraria/exportações e consumidor IVA têm entregas delimitadas em 21–22/10; restantes variantes não promovidas a concluídas.",
"evidence": "[Abrir](https://teste-investidor.tempo.co.mz/simulador-fiscal) · Formulário aberto; cenários fiscais/contributivos/exportação e parâmetros efectivos não certificados nesta execução."
},
{
"number": 12,
"component": "Negócios existentes e classificação",
"historic_status": "Disponível",
"current_preservation_status": "Módulo habilitado e fluxos conservados. Ausência de anúncios não demonstra desaparecimento de código.",
"evidence": "[Abrir](https://teste-investidor.tempo.co.mz/negocios) · Catálogo abre e módulo habilitado; zero negócios copiados. Gestão própria sujeita às permissões existentes."
},
{
"number": 13,
"component": "Negócios similares",
"historic_status": "Não demonstrado",
"current_preservation_status": "Sem desaparecimento demonstrado; conserva o âmbito parcial/não demonstrado anterior, sem promover conclusão nem reabrir provas aceites.",
"evidence": "[Abrir](https://teste-investidor.tempo.co.mz/negocios) · Código instalado; comparação com negócios reais não demonstrada."
},
{
"number": 14,
"component": "Importação de empresas/empreendimentos",
"historic_status": "Não demonstrado",
"current_preservation_status": "Sem desaparecimento demonstrado; conserva o âmbito parcial/não demonstrado anterior, sem promover conclusão nem reabrir provas aceites.",
"evidence": "[Abrir](https://teste-investidor.tempo.co.mz/management/negocios) · Mecanismo presente; nenhuma empresa/transacção local ou QA transferida. Operador de gestão por definir."
},
{
"number": 15,
"component": "Fundos",
"historic_status": "Não demonstrado",
"current_preservation_status": "Sem desaparecimento demonstrado; conserva o âmbito parcial/não demonstrado anterior, sem promover conclusão nem reabrir provas aceites.",
"evidence": "[Abrir](https://teste-investidor.tempo.co.mz/simulador-fiscal) · Fontes instaladas; critérios específicos de fundos não demonstrados no destino."
},
{
"number": 16,
"component": "Comparador societário e modelos",
"historic_status": "Não demonstrado",
"current_preservation_status": "Sem desaparecimento demonstrado; conserva o âmbito parcial/não demonstrado anterior, sem promover conclusão nem reabrir provas aceites.",
"evidence": "[Abrir](https://teste-investidor.tempo.co.mz/simulador-fiscal) · Fontes instaladas; comparação societária/modelos não demonstrados nesta execução."
},
{
"number": 17,
"component": "Capitalização/private equity e Bolsa",
"historic_status": "Não demonstrado",
"current_preservation_status": "Sem desaparecimento demonstrado; conserva o âmbito parcial/não demonstrado anterior, sem promover conclusão nem reabrir provas aceites.",
"evidence": "[Abrir](https://teste-investidor.tempo.co.mz/simulador-fiscal) · Fontes instaladas; cenários específicos de capitalização/Bolsa não demonstrados no destino."
},
{
"number": 18,
"component": "Checklists versionadas",
"historic_status": "Não demonstrado",
"current_preservation_status": "Sem desaparecimento demonstrado; conserva o âmbito parcial/não demonstrado anterior, sem promover conclusão nem reabrir provas aceites.",
"evidence": "[Abrir](https://teste-investidor.tempo.co.mz/balcao-virtual/servicos) · Definições presentes; checklist aplicada a dossier legítimo continua por demonstrar."
},
{
"number": 19,
"component": "Indicadores de utilização",
"historic_status": "Não demonstrado",
"current_preservation_status": "Sem desaparecimento demonstrado; conserva o âmbito parcial/não demonstrado anterior, sem promover conclusão nem reabrir provas aceites.",
"evidence": "[Abrir](https://teste-investidor.tempo.co.mz/dashboard) · Código presente; indicadores com dados reais e permissões adequadas não demonstrados."
},
{
"number": 20,
"component": "Jornadas educativas",
"historic_status": "Disponível",
"current_preservation_status": "Jornadas habilitadas; cartões educativos disponibilizados. Mantido o âmbito parcial da educação.",
"evidence": "[Abrir](https://teste-investidor.tempo.co.mz/educacao) · Área abre e flag habilitada; nenhum percurso educativo completo foi repetido."
},
{
"number": 21,
"component": "Partilha documental individual e variantes de sala",
"historic_status": "Não demonstrado",
"current_preservation_status": "Sem desaparecimento demonstrado; conserva o âmbito parcial/não demonstrado anterior, sem promover conclusão nem reabrir provas aceites.",
"evidence": "[Abrir](https://teste-investidor.tempo.co.mz/parcerias/painel) · Estruturas de salas/partilha instaladas; participantes, consentimentos e partilha real não demonstrados."
},
{
"number": 22,
"component": "Restauro, modalidades e SLA",
"historic_status": "Não demonstrado",
"current_preservation_status": "Sem desaparecimento demonstrado; conserva o âmbito parcial/não demonstrado anterior, sem promover conclusão nem reabrir provas aceites.",
"evidence": "[Abrir](https://teste-investidor.tempo.co.mz/balcao-virtual/painel) · Backup/restauro coberto separadamente; modalidades/SLA não homologados. Escalonamento continua desligado como local."
},
{
"number": 23,
"component": "Cartografia e consumidores territoriais restantes",
"historic_status": "Não demonstrado",
"current_preservation_status": "Demarcação reposta: 408 geometrias. Polígonos de localidades e correspondências já pendentes não são nova regressão.",
"evidence": "[Abrir](https://teste-investidor.tempo.co.mz/oportunidades) · Catálogos/mapas abrem. 1 921 referências geográficas no alvo versus 1 990 locais; diferença não reconciliada por item fora do manifesto."
},
{
"number": 24,
"component": "Acessibilidade e restantes consumidores",
"historic_status": "Não demonstrado",
"current_preservation_status": "Sem desaparecimento demonstrado; conserva o âmbito parcial/não demonstrado anterior, sem promover conclusão nem reabrir provas aceites.",
"evidence": "[Abrir](https://teste-investidor.tempo.co.mz/oportunidades) · Mensagens R2 preservadas; não se declara certificação integral de acessibilidade de todos os módulos."
},
{
"number": 25,
"component": "Fechos de operação/avaliação documental",
"historic_status": "Não demonstrado",
"current_preservation_status": "Sem desaparecimento demonstrado; conserva o âmbito parcial/não demonstrado anterior, sem promover conclusão nem reabrir provas aceites.",
"evidence": "[Abrir](https://teste-investidor.tempo.co.mz/balcao-virtual/painel) · Sem processo operacional legítimo para demonstrar fecho/avaliação documental."
},
{
"number": 26,
"component": "Conta simples, login e MFA privilegiado",
"historic_status": "Disponível",
"current_preservation_status": "Login/MFA e designações reais documentados; sem alteração de papéis nesta passagem.",
"evidence": "[Abrir](https://teste-investidor.tempo.co.mz/login) · Login e desafio MFA reais concluídos; conta simples R2 e controlos privilegiados preservados."
},
{
"number": 27,
"component": "Manifestação simples, recibo e acompanhamento do interessado — R2",
"historic_status": "Disponível",
"current_preservation_status": "R2 e percursos posteriores conservados; reutilizar as provas aceites.",
"evidence": "[Abrir](https://teste-investidor.tempo.co.mz/investidor/manifestacoes) · Recorte R2 conservado, com provas aceites reutilizadas; nenhuma manifestação fictícia criada."
},
{
"number": 28,
"component": "Pesquisa — estados do recorte R2",
"historic_status": "Disponível",
"current_preservation_status": "Pesquisa e selectores corrigidos em 22/10; a carteira vazia é tratada como perda separada.",
"evidence": "[Abrir](https://teste-investidor.tempo.co.mz/oportunidades) · Catálogos abrem; quatro estados e protecção contra respostas antigas preservados do R2, sem repetir ensaios aceites."
},
{
"number": 29,
"component": "Biblioteca privada própria, scanner e recuperação — DOC01",
"historic_status": "Disponível",
"current_preservation_status": "Biblioteca, scan e recuperação disponíveis: 59 recursos, 29 públicos e 30 privados.",
"evidence": "[Abrir](https://teste-investidor.tempo.co.mz/management/portal-services) · Conta de custódia vê 30 ficheiros limpos. Novo original recuperado no navegador com SHA-256 igual à fonte; sem gestão global."
},
{
"number": 30,
"component": "Backup/restauro e reversão do recorte R2 + DOC01",
"historic_status": "Disponível",
"current_preservation_status": "Backup/restauro 22/10 documentado: 174 tabelas. Reposição activa terá backup próprio antes da escrita.",
"evidence": "Backup fresco consistente: 144 tabelas restauradas iguais, 34 ficheiros/configuração conferidos, recuperação portável e metadados Linux/PG protegidos. Reversão de apresentação preparada."
},
{
"number": 31,
"component": "Balcão Virtual",
"historic_status": "Disponível",
"current_preservation_status": "Balcão e definições instalados; zero casos reais no destino não é perda de funcionalidade.",
"evidence": "[Abrir](https://teste-investidor.tempo.co.mz/balcao-virtual/painel) · Painel e serviço existente abrem; dez tabelas base instaladas, definição interna project-preparation v5 transportada."
},
{
"number": 32,
"component": "Vínculo positivo e recuperação do dossier",
"historic_status": "Não demonstrado",
"current_preservation_status": "Recuperação de contexto demonstrada em QA nas entregas posteriores; ausência de caso real não reabre a prova aceite.",
"evidence": "[Abrir](https://teste-investidor.tempo.co.mz/investimentos) · Vínculo documental positivo pendente: zero dossiers da conta no destino. Não foram criados dados fictícios."
},
{
"number": 33,
"component": "Cadastro canónico e recursos de oportunidade",
"historic_status": "Instalado mas desabilitado",
"current_preservation_status": "Cadastro e recursos disponibilizados; 30 oportunidades com resumos em preparação publicados.",
"evidence": "[Abrir](https://teste-investidor.tempo.co.mz/management/opportunities) · Editor/schema instalados; zero oportunidades. Gestão/revisão exigem atribuições existentes ainda não configuradas."
},
{
"number": 34,
"component": "Perfis, revisão independente e publicação",
"historic_status": "Instalado mas desabilitado",
"current_preservation_status": "Perfis e atribuições existentes preservados; inventário não cria decisão organizacional nova.",
"evidence": "[Abrir](https://teste-investidor.tempo.co.mz/perfis/gestao) · Schema e fluxos presentes; zero atribuições/titulares. Nenhum papel ou privilégio concedido automaticamente."
},
{
"number": 35,
"component": "Bolsa de parcerias e salas internas",
"historic_status": "Disponível",
"current_preservation_status": "Bolsa e percursos existentes preservados; variantes com participantes reais mantêm saldo próprio.",
"evidence": "[Abrir](https://teste-investidor.tempo.co.mz/parcerias) · Catálogo e mapa abrem, nove tabelas de relações/salas instaladas; colaboração com participantes reais não demonstrada."
},
{
"number": 36,
"component": "Onde se localiza / associações territoriais e produtivas",
"historic_status": "Ausente",
"current_preservation_status": "Estado Ausente superado: integração territorial entregue em 20–22/10; catálogos e 408 limites conservados.",
"evidence": "Harmonização territorial apenas QA, expressamente excluída da revisão autorizada."
},
{
"number": 37,
"component": "Lote privado Inhambane —30 fichas",
"historic_status": "Ausente",
"current_preservation_status": "Estado Ausente superado: 30 fichas Inhambane transportadas e resumos divulgados em 22/10.",
"evidence": "30 fichas Inhambane apenas QA, não importadas nem publicadas no destino."
},
{
"number": 38,
"component": "Enquadramento das12 fichas de programas/carteira",
"historic_status": "Ausente",
"current_preservation_status": "Estado Ausente superado: enquadramento de 11 programas e uma carteira incluído na entrega posterior.",
"evidence": "Enquadramento das 12 fichas permanece no incremento QA separado."
},
{
"number": 39,
"component": "Originais partilhados e referência documental no dossier — Inhambane",
"historic_status": "Ausente",
"current_preservation_status": "Estado Ausente superado: originais privados/referências e contexto incluídos na entrega posterior; não reimportar.",
"evidence": "Reutilização documental Inhambane/dossier permanece no incremento QA separado."
},
{
"number": 40,
"component": "Orientação assistida/IA",
"historic_status": "Instalado mas desabilitado",
"current_preservation_status": "Sem regressão demonstrada: integração externa não fechada; não inventar credencial nem activar fornecedor.",
"evidence": "[Abrir](https://teste-investidor.tempo.co.mz/management/ai) · Código presente; integração desligada. Credencial própria do destino/chamada externa não confirmadas."
},
{
"number": 41,
"component": "Integração externa SuiteCRM",
"historic_status": "Instalado mas desabilitado",
"current_preservation_status": "Desabilitada também no local por opção anterior; não desapareceu por efeito da entrega editorial.",
"evidence": "Permanece desligada como na versão local; nenhuma credencial ou envio externo copiado/executado."
},
{
"number": 42,
"component": "Operação administrativa e resposta da equipa na homologação",
"historic_status": "Instalado mas desabilitado",
"current_preservation_status": "Estado antigo superado: conta real e gestão autorizada demonstradas nas entregas posteriores. Sem novos papéis.",
"evidence": "[Abrir](https://teste-investidor.tempo.co.mz/management/interests) · Nenhuma conta com gestão global/atribuições operacionais. Custódia documental não autoriza resposta/revisão da equipa."
},
{
"number": 43,
"component": "Gestão editorial da biblioteca",
"historic_status": "Instalado mas desabilitado",
"current_preservation_status": "Gestão autónoma entregue; edição, prévia, gravação e recuperação dos 28 privados demonstradas no destino.",
"evidence": "[Abrir](https://teste-investidor.tempo.co.mz/management/portal-services) · Schema editorial instalado; publicação/edição pública não concedida à conta de custódia."
}
],
"existing_executable_surfaces": [
{
"surface": "------------------------------",
"component": "-------------------------------------------",
"routes": "-------------------------------------------------------------------------",
"accepted_evidence": "---------------------------------------------------------",
"code_files": []
},
{
"surface": "Início",
"component": "`PortalInvestidor`",
"routes": "`/`, APIs existentes de homepage",
"accepted_evidence": "`PublicPortalNavigationTest`, navegador",
"code_files": []
},
{
"surface": "Catálogo / Educação",
"component": "`investment/catalogue`",
"routes": "`/oportunidades`, `/educacao`; `/api/v1/investment/search`",
"accepted_evidence": "`InvestmentUnificationTest`",
"code_files": [
{
"path": "resources\\js\\pages\\investment\\catalogue.tsx",
"exists": true
}
]
},
{
"surface": "Cluster / Corredor / Âncora",
"component": "`portfolio/index`, `portfolio/show`",
"routes": "`/clusters`, `/corredores`, `/projectos-ancora`, `/carteira/{id}`",
"accepted_evidence": "`InvestmentPortfolioTest`, testes territoriais",
"code_files": [
{
"path": "resources\\js\\pages\\portfolio\\index.tsx",
"exists": true
},
{
"path": "resources\\js\\pages\\portfolio\\show.tsx",
"exists": true
}
]
},
{
"surface": "Ficha",
"component": "`investment/detail`",
"routes": "`/oportunidades/{slug}`; `/api/v1/investment/opportunities/{slug}`",
"accepted_evidence": "`PublicPortalNavigationTest`, `InvestmentUnificationTest`",
"code_files": [
{
"path": "resources\\js\\pages\\investment\\detail.tsx",
"exists": true
}
]
},
{
"surface": "Mapa",
"component": "Componente `Map` em catálogo",
"routes": "`/api/v1/investment/map`",
"accepted_evidence": "Teste PostGIS e estados vazios",
"code_files": []
},
{
"surface": "Comparar / exportar / sugerir",
"component": "`investment/catalogue`",
"routes": "`/compare`, `/export`, `/suggest` sob prefixo da API",
"accepted_evidence": "Teste de publicação e fórmula CSV",
"code_files": [
{
"path": "resources\\js\\pages\\investment\\catalogue.tsx",
"exists": true
}
]
},
{
"surface": "Área do investidor",
"component": "`investment/workspace`",
"routes": "`/investidor/manifestacoes`; `/api/v1/investment/submissions`",
"accepted_evidence": "Isolamento por dono",
"code_files": [
{
"path": "resources\\js\\pages\\investment\\workspace.tsx",
"exists": true
}
]
},
{
"surface": "Perfil / empresas",
"component": "`investment/profile`",
"routes": "`/investidor/profile`; API `/profile`, `/companies/{id?}`",
"accepted_evidence": "`InvestmentWorkspaceTest` e navegador",
"code_files": [
{
"path": "resources\\js\\pages\\investment\\profile.tsx",
"exists": true
}
]
},
{
"surface": "Conversa / versões",
"component": "`investment/conversation`",
"routes": "API `/submissions/{id}/messages`, `/submissions/{id}/revisions/{version}`",
"accepted_evidence": "Testes de isolamento, encriptação e reenvio; navegador",
"code_files": [
{
"path": "resources\\js\\pages\\investment\\conversation.tsx",
"exists": true
}
]
},
{
"surface": "Notificações",
"component": "`investment/notifications`",
"routes": "`/investidor/notifications`; API `/notifications`, `/{id}/read`",
"accepted_evidence": "Testes de mudança de estado e leitura; navegador",
"code_files": [
{
"path": "resources\\js\\pages\\investment\\notifications.tsx",
"exists": true
}
]
},
{
"surface": "Manifestação",
"component": "`investment/submission`",
"routes": "`PUT /submissions/{id}`, `POST /submissions/{id}/submit`",
"accepted_evidence": "Testes de conflito, validação e reenvio",
"code_files": [
{
"path": "resources\\js\\pages\\investment\\submission.tsx",
"exists": true
}
]
},
{
"surface": "Anexos",
"component": "Etapa 4 da manifestação",
"routes": "`/submissions/{id}/attachments`",
"accepted_evidence": "Teste de anexo privado e adulteração",
"code_files": []
},
{
"surface": "Contexto da oportunidade",
"component": "`investment/context`",
"routes": "`/management/opportunities/{id}/context`",
"accepted_evidence": "Teste de gestão, conflito e educação",
"code_files": [
{
"path": "resources\\js\\pages\\investment\\context.tsx",
"exists": true
}
]
},
{
"surface": "Referências e formulários",
"component": "`investment/references`",
"routes": "`/management/investment/references`",
"accepted_evidence": "Teste de referências e reexecução do seed",
"code_files": [
{
"path": "resources\\js\\pages\\investment\\references.tsx",
"exists": true
}
]
},
{
"surface": "Atendimento / CRM",
"component": "`management/interest`, gestão CRM existente",
"routes": "`/management/interests/{id}`",
"accepted_evidence": "Testes de intake e CRM existentes",
"code_files": [
{
"path": "resources\\js\\pages\\management\\interest.tsx",
"exists": true
}
]
},
{
"surface": "Apoio / documentos / parcerias",
"component": "`portal/services`, `portal/manage`",
"routes": "`/apoio`, `/documentos`, `/parcerias`, `/balcao-virtual`",
"accepted_evidence": "`PublicPortalServicesTest`",
"code_files": [
{
"path": "resources\\js\\pages\\portal\\services.tsx",
"exists": true
},
{
"path": "resources\\js\\pages\\portal\\manage.tsx",
"exists": true
}
]
}
],
"additional_approved_increment": "Concursos e anúncios: instalado e habilitado em 22/10; gestão autorizada e consulta, sem anúncio real publicado.",
"source_inventory_sha256": {
"local": "135bdc35b23d05e239f9c44ef9762af11111c880b4ef78faaacb63ebeb9dfdc2",
"qa": "632a488d2a0a7841babceb2242f6be61d534b54b64958db0d639185aabb80104",
"homologation": "a247f521b512654206a1fbf4fde03a46233d8e2d98122d119417bc0a2f70df23"
},
"confirmed_loss": {
"portfolio_and_anchor_visibility": {
"expected_published": 29,
"current_target_published": 0,
"records_present": 29,
"deletion_claimed": false,
"local_QA_content_source_matches": 29,
"target_hash_match": 29,
"recovery_status": "restored in QA and homologation",
"target_visible_after": 29
}
},
"historic_change": {
"commit": "e65cb26901a5ca18862134b20e4c17b1f1003ec2",
"date": "2026-10-06",
"change": "Removed portfolio:import --approval and document_owner publication path; import now creates drafts",
"does_not_prove_date_of_disappearance_from_particular_address": true
},
"qa_tests_run": false,
"facts_before_recovery": {
"local": {
"dossiers": 29,
"portfolio_visible": 29,
"projects": 16,
"clusters": 8,
"corridors": 6,
"resources": 29,
"geometries": 0
},
"qa": {
"dossiers": 29,
"portfolio_visible": 0,
"projects": 16,
"clusters": 8,
"corridors": 6,
"resources": 61,
"geometries": 408
},
"homologation": {
"dossiers": 29,
"portfolio_visible": 0,
"projects": 16,
"clusters": 8,
"corridors": 6,
"resources": 59,
"geometries": 408
}
},
"inventory_data_writes": 0,
"qa_recovery": {
"database": {
"name": "simulator_fiscal_delta_20261021_test",
"port": 55441
},
"qa": true,
"mode": "applied",
"manifest_sha256": "8d07007a2e1e8758d4741b4ac594f78d5943f71ad78b2daae7f85aefa9d2de4a",
"preview_sha256": "fbb9ba70df67ef349cb673a683f051f21a8ae62d192f09fc35f2abac7c45b35d",
"scope": 29,
"restored": 29,
"visible": 29,
"data_content_changed": false,
"new_dossiers_imported": 0,
"fake_editor_or_reviewer_assigned": false,
"plan": [
{
"id": "9871a8c0-d562-420b-a8f7-174c95db93f0",
"code": "A01",
"version": 1,
"status": "draft",
"content_sha256": "2adcbe741777d4352c47eb31281dc6b2e2d04903fcfd3e59778ddff4e1342977",
"source_sha256": "4919cd8aae6230e576947b38e5ff839b2661166fb2a8598bea1d662f15bd170a",
"already_restored": false
},
{
"id": "64b824a0-3527-4024-a1a2-5234f5b4caaa",
"code": "A02",
"version": 1,
"status": "draft",
"content_sha256": "3ad808813909f1475f44a5fcf2a4e50c5d01e25ad8ea39d906c52519532cf0ee",
"source_sha256": "4919cd8aae6230e576947b38e5ff839b2661166fb2a8598bea1d662f15bd170a",
"already_restored": false
},
{
"id": "6c03e980-c455-49cc-a3be-99d29014e91d",
"code": "A03",
"version": 1,
"status": "draft",
"content_sha256": "75266ff89da58e4d6e5422e49e4641c62dae4f171fa358b70e210c5883a1fd07",
"source_sha256": "4919cd8aae6230e576947b38e5ff839b2661166fb2a8598bea1d662f15bd170a",
"already_restored": false
},
{
"id": "8bd83cc4-b36c-46cd-9287-b47e1162b578",
"code": "A04",
"version": 1,
"status": "draft",
"content_sha256": "f6a63e2ee2f2e7c98f1030d206996ad57b0d92129d021ed3c6bb53f161d56f5b",
"source_sha256": "4919cd8aae6230e576947b38e5ff839b2661166fb2a8598bea1d662f15bd170a",
"already_restored": false
},
{
"id": "d8a88bac-4684-43fa-aea6-e54fef9b8a47",
"code": "A05",
"version": 1,
"status": "draft",
"content_sha256": "ea0f6b7843289d81dc2852327432721f8677e28ad8b41d754abc8c91307303d3",
"source_sha256": "4919cd8aae6230e576947b38e5ff839b2661166fb2a8598bea1d662f15bd170a",
"already_restored": false
},
{
"id": "57a4ee7a-4c07-4e26-b8f5-e07726e41512",
"code": "A06",
"version": 1,
"status": "draft",
"content_sha256": "c7fae7b2ca541e734e587589dace8dc85dee2494876de6e30ecd91a7177a985d",
"source_sha256": "4919cd8aae6230e576947b38e5ff839b2661166fb2a8598bea1d662f15bd170a",
"already_restored": false
},
{
"id": "70ae731e-fdef-473f-b495-14f319b28d60",
"code": "A07",
"version": 1,
"status": "draft",
"content_sha256": "3eb01ce3dd2cc68b43b816708f7e5f990c48f1aea6555cdfd169a286e11b9bd4",
"source_sha256": "4919cd8aae6230e576947b38e5ff839b2661166fb2a8598bea1d662f15bd170a",
"already_restored": false
},
{
"id": "b30ecd8c-220d-4c7a-b1a7-704dc24d0933",
"code": "A08",
"version": 1,
"status": "draft",
"content_sha256": "bca089a4bb2601ca34c50e2c48068b4c2f9fa7a873955cf0587f261a83c2a146",
"source_sha256": "4919cd8aae6230e576947b38e5ff839b2661166fb2a8598bea1d662f15bd170a",
"already_restored": false
},
{
"id": "dc8892ff-fdda-480c-81f1-2ffe0bfcb40b",
"code": "A09",
"version": 1,
"status": "draft",
"content_sha256": "7e3bb071f65e49245fbfd070f4123ac49985004e865c475bf7e34c85cc708aea",
"source_sha256": "4919cd8aae6230e576947b38e5ff839b2661166fb2a8598bea1d662f15bd170a",
"already_restored": false
},
{
"id": "77b685f0-848f-4800-83f8-a29539ed8f31",
"code": "A10",
"version": 1,
"status": "draft",
"content_sha256": "02dc0fb152908e81b11304f6bc0c2252805ebd2ad1860b1a6bfa918e2cbcd5bb",
"source_sha256": "4919cd8aae6230e576947b38e5ff839b2661166fb2a8598bea1d662f15bd170a",
"already_restored": false
},
{
"id": "cb125f84-6f75-48e5-a0de-2f5e6bd537ed",
"code": "A11",
"version": 1,
"status": "draft",
"content_sha256": "736fc41d941740f41697c579ffe6c4208d45005d7177b7c42f006554a5dde480",
"source_sha256": "4919cd8aae6230e576947b38e5ff839b2661166fb2a8598bea1d662f15bd170a",
"already_restored": false
},
{
"id": "71b96599-09c2-4809-b98f-acbf0c2b6f38",
"code": "A12",
"version": 1,
"status": "draft",
"content_sha256": "9dbdb468b8644e07fabdc943ecde2f11ea561d5174c00559228ed29b2a29b1fe",
"source_sha256": "4919cd8aae6230e576947b38e5ff839b2661166fb2a8598bea1d662f15bd170a",
"already_restored": false
},
{
"id": "5ce2d415-1c84-4625-a4cd-0f6738454318",
"code": "A13",
"version": 1,
"status": "draft",
"content_sha256": "7a62afdda8e2c3fcc47b1c9032359906ff75dff9b5d56c71ba7d88781a8f8620",
"source_sha256": "4919cd8aae6230e576947b38e5ff839b2661166fb2a8598bea1d662f15bd170a",
"already_restored": false
},
{
"id": "88111fb4-ab20-4393-bd1b-3a1694fb4ac4",
"code": "A14",
"version": 1,
"status": "draft",
"content_sha256": "930836392126133499fa69313a4718ce2ec1f6c8f0839c184e9b5cc229460d1c",
"source_sha256": "4919cd8aae6230e576947b38e5ff839b2661166fb2a8598bea1d662f15bd170a",
"already_restored": false
},
{
"id": "88e37c63-e785-446f-9074-84b000bffa59",
"code": "A15",
"version": 1,
"status": "draft",
"content_sha256": "d19d27451296b9816351e2d340f2aaff9fabf9732fb580d179e1c69ceb9fc512",
"source_sha256": "4919cd8aae6230e576947b38e5ff839b2661166fb2a8598bea1d662f15bd170a",
"already_restored": false
},
{
"id": "0c63d8de-18fd-451f-a7ca-1ea04dd5906c",
"code": "CLU-01",
"version": 1,
"status": "draft",
"content_sha256": "ccd16f969474dcc23b2ddf27eb4cf3ca090f22e418e0e8f33499f349e884d042",
"source_sha256": "70688ba4623298f5ec073331c06c28ca5b0a9c552578e22ccebe20c27d1ca8cf",
"already_restored": false
},
{
"id": "fc1355f5-1473-4fe2-b3e2-b32d1f0b1042",
"code": "CLU-02",
"version": 1,
"status": "draft",
"content_sha256": "78c50659068301be0db94e8f4c9e77a20a4a052639dc4553291ff3568d3c0c4a",
"source_sha256": "70688ba4623298f5ec073331c06c28ca5b0a9c552578e22ccebe20c27d1ca8cf",
"already_restored": false
},
{
"id": "99bf7c4c-e23b-47d5-8490-53082384043f",
"code": "CLU-03",
"version": 1,
"status": "draft",
"content_sha256": "99af7bb35b6769d84bee2175427b3a03027fea65a91dd894338fd7aa72db3820",
"source_sha256": "70688ba4623298f5ec073331c06c28ca5b0a9c552578e22ccebe20c27d1ca8cf",
"already_restored": false
},
{
"id": "816b7c26-827b-475a-bf98-647b46afe88b",
"code": "CLU-04",
"version": 1,
"status": "draft",
"content_sha256": "a75f532e2464dadd13dd2ff815d99acfb2f05d3a0264fcf82dfe8e3db7db3a1f",
"source_sha256": "70688ba4623298f5ec073331c06c28ca5b0a9c552578e22ccebe20c27d1ca8cf",
"already_restored": false
},
{
"id": "8eb7da82-1f1e-442b-9b1e-57b948c3f057",
"code": "CLU-05",
"version": 1,
"status": "draft",
"content_sha256": "fe2f37be8e3f20ccc35da91aa86872b960e9820f2e0de84200c157072b3c7b11",
"source_sha256": "70688ba4623298f5ec073331c06c28ca5b0a9c552578e22ccebe20c27d1ca8cf",
"already_restored": false
},
{
"id": "65d709f5-20ed-4beb-8cf1-4a7fbd459e8f",
"code": "CLU-06",
"version": 1,
"status": "draft",
"content_sha256": "097498df28f63b614914cc171e4d58c6154442d0d6625185b3759878829528de",
"source_sha256": "70688ba4623298f5ec073331c06c28ca5b0a9c552578e22ccebe20c27d1ca8cf",
"already_restored": false
},
{
"id": "a795cde3-3cfe-456b-ba8d-7ff3730024ec",
"code": "CLU-07",
"version": 1,
"status": "draft",
"content_sha256": "6c5407281d18069e05b84e1e7997d07fd79680e2b0e2a5b0e7d86cbfc3c2ca35",
"source_sha256": "70688ba4623298f5ec073331c06c28ca5b0a9c552578e22ccebe20c27d1ca8cf",
"already_restored": false
},
{
"id": "05dc87a8-e1ab-41c9-a312-5b14b8ff16ce",
"code": "CLU-08",
"version": 1,
"status": "draft",
"content_sha256": "f59505944a91a849c27c16455001ad4a1d39aa58e59e8d5403cb0dc197c2faf8",
"source_sha256": "70688ba4623298f5ec073331c06c28ca5b0a9c552578e22ccebe20c27d1ca8cf",
"already_restored": false
},
{
"id": "986af8c5-2f90-40ab-a62a-4c37a5f42e30",
"code": "COR-01",
"version": 1,
"status": "draft",
"content_sha256": "26db6621cae0e76d2871f56926062bc0be5bb41e21521d2536655e809b6dff20",
"source_sha256": "09a6a7664f6a910dbc22190ab85d832f5405dd3eaf19e712f313c4241b873b55",
"already_restored": false
},
{
"id": "a7271336-808b-42b4-8bfd-5b49c72dbe2b",
"code": "COR-02",
"version": 1,
"status": "draft",
"content_sha256": "0e6b80849fcb0746aa1a53133ee0d5ccc3df8c847243a1134922247ce3d9e1b8",
"source_sha256": "09a6a7664f6a910dbc22190ab85d832f5405dd3eaf19e712f313c4241b873b55",
"already_restored": false
},
{
"id": "d9ccddb5-e6aa-4347-a481-37b694013ac1",
"code": "COR-03",
"version": 1,
"status": "draft",
"content_sha256": "64c8eba636661e6ebb56e2dd8dd6e978c405dd96c0c7b46bdd62c805018a6c4a",
"source_sha256": "09a6a7664f6a910dbc22190ab85d832f5405dd3eaf19e712f313c4241b873b55",
"already_restored": false
},
{
"id": "ae26d529-5341-4b37-ad08-662c9d09a0cf",
"code": "COR-04",
"version": 1,
"status": "draft",
"content_sha256": "ce5b89974e1d2937e4901ab5603245cf2465afbe0a44a224c43f726069cdabd8",
"source_sha256": "09a6a7664f6a910dbc22190ab85d832f5405dd3eaf19e712f313c4241b873b55",
"already_restored": false
},
{
"id": "69767dcc-4c22-4046-8f81-3797bc44572e",
"code": "COR-05",
"version": 1,
"status": "draft",
"content_sha256": "343ba56efeef5514f531cf61126596a8c65f882656da5f95390eb661a0810a7c",
"source_sha256": "09a6a7664f6a910dbc22190ab85d832f5405dd3eaf19e712f313c4241b873b55",
"already_restored": false
},
{
"id": "fb85ebfc-798c-42df-a267-d4b771c443f8",
"code": "COR-06",
"version": 1,
"status": "draft",
"content_sha256": "465ff9717def3bad42efb25d1ffa04812f871e8b167adcbb20f68f5ef9460b57",
"source_sha256": "09a6a7664f6a910dbc22190ab85d832f5405dd3eaf19e712f313c4241b873b55",
"already_restored": false
}
]
},
"destination_recovery": {
"database": {
"name": "teste_investidor",
"port": 5432
},
"qa": false,
"mode": "applied",
"manifest_sha256": "8d07007a2e1e8758d4741b4ac594f78d5943f71ad78b2daae7f85aefa9d2de4a",
"preview_sha256": "6302eee1c69b44e726fc1e0b2c5a63ac0d5eb76b23eaf97295475ab411c1151e",
"scope": 29,
"restored": 29,
"visible": 29,
"data_content_changed": false,
"new_dossiers_imported": 0,
"fake_editor_or_reviewer_assigned": false
},
"preservation_after": {
"changed_tables": [
"audit_events",
"investment_dossiers",
"sessions"
],
"expected_recovery_changes": [
"investment_dossiers",
"audit_events"
],
"unchanged_tables": 171,
"table_count": 174,
"portal_resources_unchanged": true,
"documents_unchanged": true,
"geometries_unchanged": true,
"role_assignments_unchanged": true,
"new_dossiers": 0,
"deployment_watcher": "active"
},
"backup": {
"stage": "/var/backups/investmoz-preservation-inventory-20261023/backup",
"target": {
"database": "teste_investidor",
"port": 5432
},
"tables": 174,
"files_verified": 369,
"dump_sha256": "7b7a3a082e1259c53c321617eb84d10b544e5a211f5f708199026c4205d1c511",
"files_sha256": "0cc28141a081ce9d097735c7815b46f55d99300adf7361d936b857a24f897b86",
"restore_verified": false
},
"restore": {
"restore_verified": true,
"tables_verified": 174,
"container_network": "none",
"database": "preservation_restore_20261023",
"port": 55441,
"source_dump_sha256": "7b7a3a082e1259c53c321617eb84d10b544e5a211f5f708199026c4205d1c511",
"active_database_writes": 0
},
"public_recovery": {
"checked_utc": "2026-10-23T07:12:24.984213+00:00",
"results": [
{
"url": "https://teste-investidor.tempo.co.mz/carteira",
"http": 200,
"component": "portfolio/index",
"total": 29,
"authenticated": false
},
{
"url": "https://teste-investidor.tempo.co.mz/carteira?kind=project",
"http": 200,
"component": "portfolio/index",
"total": 15,
"authenticated": false
},
{
"url": "https://teste-investidor.tempo.co.mz/carteira?kind=cluster",
"http": 200,
"component": "portfolio/index",
"total": 8,
"authenticated": false
},
{
"url": "https://teste-investidor.tempo.co.mz/carteira?kind=corridor",
"http": 200,
"component": "portfolio/index",
"total": 6,
"authenticated": false
}
]
},
"new_contents_imported": 0,
"executed_recovery_source_sha256": "f4f76fa8b41bb5cf241093709ea8044592a54f8bafd0df9ca15b0c8745c45bcc",
"active_recovery_changes": {
"investment_dossiers": 29,
"audit_events": 29,
"credentials_stored": false,
"new_roles_or_approvers": false,
"new_content_approval": false
},
"qa_recovery_preparation_note": "An initial QA audit-key collision rolled back atomically. Audit identity was scoped per original before the successful QA and destination recovery; no active attempt used the faulty key."
}
@@ -1,5 +1,277 @@
# Rastreabilidade, utilização e formação
## Instalação e conferência das três correcções — 23/10/2026
**Revisão servida: `6b544548`.** Conferência visual QA da Beira concluída antes da integração: sete oportunidades visíveis, Florestas abre com origem da carteira conservada nos dois botões de preparação. Integrada por avanço directo no Git, sem alterações concorrentes em main. Executor concluído às 13:45:19 UTC, 206/206 testes frontend; zero migrations/importações e nenhuma alteração de permissões. Recibo: `continuity-corrections-installation-receipt-20261023.json`. As referências abaixo a QA pendente/homologação em `7c1f5321` descrevem o estado histórico anterior, sucedido por esta instalação.
Delta no destino, apenas no dossier privado já identificado como teste `958df138-7980-4685-8389-fa90be02ed6b`: seleccionar Português (Moçambique), Guardar e continuar mais tarde e recarregar recuperou o idioma, com confirmação e sem falso conflito. Relatório imprimível revisão 4 identifica título herdado dos dados introduzidos no dossier, resumo herdado da oportunidade Florestas v2, sector herdado do catálogo sectorial e Sofala do catálogo territorial; sem repetir o teste de todas as exportações/paginação. A guarda do servidor coincide por SHA-256 com a testada em QA; reutilizada a recusa de versões antigas, sem provocar escrita desactualizada em dados do destino.
Na ficha pública da Beira, as sete ligações foram abertas individualmente: Açúcar, Carne bovina, Florestas, Hortícolas, Milho/trigo/arroz, Sésamo e Soja. Títulos coincidentes e origem `from_dossier` conservada em todos os botões de preparação. Não foram publicadas novamente as 34 oportunidades nem alteradas relações por província. Provas e revisão anterior preservadas no recibo. Aplicação transversal continua parcial; este fecho respeita apenas os três deltas pedidos.
## Correcções da verificação de homologação — candidata QA, 23/10/2026
Referência: `c3f7c3ad`; pedido expresso da Tempo nas três prioridades. As 34 oportunidades já públicas e as provas aceites mantêm-se. Esta candidata não altera a homologação, que permanece em `7c1f5321`; sem migrations, novos papéis, publicação, importação ou alteração de dados activos.
1. **Gravação/idioma — causa de transporte reproduzida.** O Nginx do destino comprime JSON e enfraquece o ETag da representação; confirmado em leitura pública do questionário (o controlador produz uma versão forte e a resposta chega `W/"questionnaire-…"`, com gzip). O cliente do rascunho devolvia literalmente o cabeçalho ao servidor, que compara a versão forte exacta; os logs do dossier de teste mostram PUT 200 seguido de 412 e repetição após recuperação. Em QA isolado, um router de prova aplica gzip e enfraquece os ETags dos rascunhos para reproduzir esse transporte. A correcção normaliza apenas o formato conhecido `W/"sfd-UUID-vN"` antes de o usar; não aceita outra versão, não muda o controlo do servidor nem força retry/overwrite. No navegador QA, mudar o idioma para English, guardar e recarregar recuperou English com confirmação de gravação, título deliberado e escolhas preservados, sem falso conflito. A leitura pública comprova o transporte no destino; a correcção do rascunho ainda aguarda instalação/conferência desse delta na homologação. Sem consulta de credenciais ou bypass de sessão/RLS.
2. **Proveniência do relatório.** A origem existente por campo estava no dossier, mas não viajava no snapshot usado pelo relatório; as linhas tinham atribuições genéricas fixas ao utilizador. O snapshot passa a incluir apenas tipo/título/versão/revisão da proveniência, sem IDs de actores. O modelo comum de relatório/CSV usa também as assinaturas de herança já existentes: mostra origem na oportunidade/catálogo/dados do utilizador no dossier; uma alteração posterior no simulador é identificada com a origem anterior. Não muda valores nem reescreve relatórios emitidos; metadados ausentes de versões antigas não são inventados. Conferido no relatório QA: resumo herdado da oportunidade, título alterado posteriormente e referência da oportunidade/localização/associação conservadas. Captura `outputs/preservation-inventory-20261023/corrected-provenance-report-QA.png`.
3. **Beira → oportunidades.** A consulta da carteira só procurava pivots; o cadastro das 34 conserva a pertença documental no `workflow_context`, deliberadamente sem inferir relações com mestres. As fontes aceites distinguem COR-02 «Corredor da Beira e Centro» (`investment-portfolio.json`) e o grupo «Corredor da Beira» (`corridor-investment-opportunities.json`): sete entradas, não selecção por província. Esta correcção implementa uma referência documental delimitada entre essa ficha e esse grupo, sem renomear/fundir corredores, acrescentar pivots ou inferir implantação. A consulta exige a chave original e o hash da fonte aceite; outras oportunidades/corredores não entram por semelhança. A mesma resolução serve a ficha, a pesquisa por corredor, a validação da origem no detalhe e a associação escolhida no dossier. As sete fichas são Milho, trigo e arroz; Soja; Hortícolas; Sésamo; Açúcar; Florestas; Carne bovina — todas no Corredor da Beira. Não se estende esta correspondência aos outros corredores, clusters ou âncoras por inferência.
Validação delimitada: 26 testes frontend afectados passaram; novo teste de proveniência/ETag repetido após o ajuste de designações, dois aprovados. Três testes transaccionais de continuidade/Beira, 91 asserções, na base QA `simulator_fiscal_delta_20261021_test:55441`, incluindo sete resultados, pesquisa, detalhe/origem e recusa de outra oportunidade. Um teste adicional, cinco asserções, confirma que o servidor continua a recusar ETag fraco e versão antiga e aceita apenas a versão forte actual. Types/build aprovados. A conferência visual final da navegação da carteira QA foi interrompida pela automação do navegador; não se declara uma prova visual nova dessa ficha, coberta aqui pela resposta Inertia/API e validação de continuidade. Mantêm-se as provas anteriores. Matriz existente: evidências acrescidas em AUD-SIM-INT03/06/07, sem novo backlog. Aplicação transversal permanece parcial.
## Verificação do produto na homologação — 23/10/2026
Origem: pedido da Tempo de avaliar carteira → escolha → dossier → simulador → relatório, registando apenas diferenças e reutilizando as provas aceites. Revisão servida confirmada: `7c1f5321`. Sem alteração de código, permissões, originais ou publicações nesta passagem. Referências da matriz existente: AUD-SIM-INT03 (recuperação), AUD-SIM-INT06 (relatórios) e AUD-SIM-INT07 (gravação/erros).
Criado pela interface um dossier privado explicitamente denominado **TESTE DE HOMOLOGAÇÃO — continuidade 23-10-2026**, `958df138-7980-4685-8389-fa90be02ed6b`, rascunho do simulador `6c07a968-7ff9-42fa-b252-072bb1acf221`. Cenário sintético, não investimento real nem manifestação/submissão. Os dossiers anteriores permaneceram sem edição. A escolha Florestas — Corredor da Beira foi recebida; o corredor já vinha seleccionado e apresentou apenas Manica/Sofala. Escolhida Sofala e actividade florestal para o teste, a prévia propôs resumo/sector/localização e preservou o título deliberado do teste. Após confirmação, oportunidade, Agronegócio, Corredor da Beira e Sofala recuperados no dossier e no simulador sem preenchimento repetido. Necessidade de terra Não, identificada como cenário sintético: acção Terra/DUAT e etapa de regularização retiradas do dossier; Situação do terreno ocultada no simulador com explicação. Não se repete a prova de novo login já aceite em QA.
Diferenças observadas, sem declarar causa ou correcção não demonstradas:
- **Gravação/recuperação (AUD-SIM-INT03/07):** no novo rascunho e num único separador desta verificação, após escolher Simples/Português e navegar nas etapas surgiu «alterado noutro separador». Recuperar a versão mais recente conservou Simples e os dados herdados, mas não o idioma. Escolher novamente o idioma e Guardar e continuar mais tarde voltou a indicar gravação não confirmada/conflito. Não há outro separador deste dossier de teste; não atribuir a falha ao utilizador nem afirmar causa técnica confirmada. Não contornado o controlo de versão.
- **Origem no relatório (AUD-SIM-INT06):** o relatório imprimível contém o resumo e Agronegócio herdados, mas classifica-os como declarados/seleccionados pelo utilizador. A proveniência apresentada deve distinguir herança da oportunidade de edição deliberada. A selecção do território foi efectivamente feita no teste.
- **Carteira → escolha:** a ficha Corredor da Beira e Centro abre, mas informa não ter oportunidades publicadas associadas nem relações disponíveis; por essa ficha não foi possível demonstrar a passagem contextual à oportunidade. Florestas também apresenta ausência de relações publicadas. A falta de associações já estava identificada; esta observação localiza o limite no destino, não prova perda recente nem autoriza inferir/recriar ligações. O dossier antigo Florestas `90110be6-00c1-4e37-a82f-19e734f4e097` abriu sem oportunidade/sector/localização confirmados; o título não prova esses dados guardados. Foi preservado.
**Relatório:** a opção Imprimir / guardar como PDF gerou uma nova apresentação, lida no navegador. Inclui as onze secções seleccionadas, oportunidade/Sofala, necessidade de terra Não e preparação do terreno não aplicável, lacunas como Não informado e ausência de plano/cálculo sem zeros presumidos. Cabeçalho/tabelas legíveis na captura; não se declara nova conferência de paginação PDF ou de todas as páginas impressas. O relatório foi gerado com o aviso de gravação activo: não atesta que todas as últimas edições locais estavam persistidas. A tentativa de captura de download Word pela automação não terminou; isso não basta para declarar defeito do exportador nem download/abertura Word demonstrados. As provas QA desses formatos mantêm-se, sem substituir a lacuna observada de gravação.
**Correcção de saldo das 34 oportunidades:** a pendência de disponibilização não corresponde ao destino actual. O catálogo mostra as entradas; a exportação pública Agronegócio devolveu 34 referências, todas coincidentes por URL com `agribusiness-homologation-20261023.json`, zero ausentes. Não houve reimportação, nova publicação ou conferência individual de todas as páginas. Disponibilidade pública e associações à carteira são estados distintos.
Provas locais: `outputs/preservation-inventory-20261023/product-report-homologation-20261023.png` e `product-simulator-save-conflict-homologation-20261023.png`. Saldo: herança, localização e não aplicabilidade demonstradas neste cenário instalado; gravação/recuperação com falha nova; proveniência do relatório a corrigir; passagem por associações reais da carteira não demonstrada neste recorte. Aplicação transversal parcial. Nenhum fecho global do produto.
## Retoma e recuperação da continuidade — conferência QA, 23/10/2026
**Estado posterior: instalada na homologação.** A revisão `7c1f5321`, que inclui `6948ed62`, `3d877ec7`, `115c7731` e a correcção histórica do relatório, está efectivamente servida desde 23/10/2026 às 13:16 UTC. Recibo: `adaptive-portfolio-installation-receipt-20261023.json`. Conferidos revisão pública, assets do delta, hashes dos cinco ficheiros afectados e carteira anónima: 29 fichas, zero IDs ausentes e zero títulos divergentes. Executor: 204/204 testes frontend; zero migrations/importações e nenhuma alteração de permissões. Reutilizadas as provas privadas QA; não se gravou um dossier real no destino. As menções abaixo a homologação inalterada conservam o estado histórico da preparação QA, agora sucedido por este recibo. Aplicação transversal permanece parcial.
Disponibilização autorizada pela Tempo: `115c7731` integrado em `main` por avanço directo, sem alterações concorrentes no remoto, migrations ou alterações de dependências/permissões. A primeira execução automática preservou a release anterior `e1f010f9`: 203/204 testes frontend passaram; o teste do documento histórico não aplicável detectou que a simplificação do relatório ocultava também esse registo. Correcção delimitada: manter a linha histórica quando existe documento associado, sem voltar a exigir o requisito; os restantes não aplicáveis conservam a explicação breve. Verificados 17 testes afectados de relatórios, continuidade e terra, mais types. Não foram repetidas as provas funcionais QA nem alterados dados no destino. A integração Git não é recibo de instalação; confirmar a revisão servida antes de declarar disponível.
Ponto de retoma da Tempo: 29 fichas visíveis; completar a continuidade, sem repetir a reposição ou as provas aceites. A implementação `3d877ec7` sucede a `6948ed62` e permanece exclusivamente em QA. As secções históricas abaixo que identificam a perda de `from_dossier` descrevem o diagnóstico anterior: essa lacuna está corrigida neste recorte QA, ainda não na homologação.
Conferência pela interface, apenas com conta e registos SYNTHETIC QA: a oportunidade, Sofala e a associação escolhida chegaram ao dossier e ao simulador. Foi guardado no simulador o título deliberadamente diferente «SYNTHETIC QA — edição deliberada de continuidade». Regressar ao dossier e reabrir Plano e simulador conservou esse título; recarregar também o conservou. Depois de Terminar sessão e efectuar novo login normal, abriu-se o mesmo investimento pelo painel e o seu Plano e simulador: oportunidade, localização, associação e título deliberado foram recuperados, sem novo preenchimento. O título do dossier e o da oportunidade não foram substituídos pela edição do simulador. O login conduziu ao painel; a recuperação foi concluída pela ligação do investimento nesse painel, não se afirma redireccionamento automático ao simulador.
Prova local: `outputs/preservation-inventory-20261023/portfolio-continuity-recovery-login-QA.png`. O dossier sintético usado é `b5248bb8-5ada-445f-a34a-e4c628b6d457`; o rascunho recuperado é `b56a6bb8-c5df-4f22-84d0-07534d9dd8ba`. Nenhum dado real ou permissão foi alterado para esta conferência.
Relatórios: reutilizadas as provas aceites de `tests/frontend/investment-portfolio-continuity.test.mjs` para o modelo comum de relatório/CSV/impressão, e as provas de aplicabilidade e situação do terreno da entrega reconciliada. Não se declara uma nova exportação visual nesta passagem nem se repete a suite. A causa e prevenção da perda anterior permanecem na secção seguinte e no manifesto/recibo existentes. Homologação inalterada; aplicação transversal ainda parcial; associações ausentes não foram inventadas.
## Continuidade carteira → oportunidade → dossier → simulador — incremento QA, 23/10/2026
Origem: pedido expresso da Tempo de conservar oportunidade, localização e associações escolhidas no mesmo percurso, sem repetir perguntas ou sobrescrever edições deliberadas. Complementa a entrega QA `6948ed62`; não instala esse conjunto nem este incremento na homologação.
Os dois botões Preparar investimento da oportunidade passam a transportar a origem `from_dossier` já validada e os filtros territoriais escolhidos. A entrada mostra a oportunidade, localização proposta e associação da carteira antes de escolher/criar o dossier. As mesmas referências atravessam autenticação, rascunho por actor, recuperação, prévia, confirmação e a referência guardada do simulador. Permanecem no payload cifrado existente: sem migrations, papéis ou percurso paralelo. Posto/localidade não são inferidos quando faltam.
Cada oportunidade conserva as suas próprias associações escolhidas (`portfolio_origins`), com identificador, título, versão, ligação e hash documental. O servidor só aceita fichas publicadas da carteira com ligação cadastrada à oportunidade. Não cria relações por semelhança de nome, corredor ou proximidade e não publica fichas privadas. É possível retirar uma associação pela interface sem retirar a oportunidade. Uma mesma associação pode acompanhar oportunidades diferentes, mantendo cada identidade e localização.
Guia: abra uma oportunidade associada a partir da carteira e escolha Preparar investimento. Na entrada, confirme visualmente a oportunidade, localização recebida e associação; continue um dossier existente ou crie um novo. Reveja e guarde no editor existente. A confirmação retira da URL a intenção de entrada já consumida, para que actualizar recupere o contexto guardado, incluindo associações retiradas, em vez de reaplicar essa entrada. Abra Plano e simulador no mesmo dossier; os dados e referências continuam disponíveis, com os controlos existentes para editar divergências e exportar.
Ao retomar uma oportunidade já existente num dossier, acrescentam-se as associações recebidas sem substituir localização, corredor ou actividades guardadas. Uma localização recebida diferente é apresentada como divergência, mantendo a anterior para revisão explícita. Confirmar associações gera a revisão/auditoria do contexto existente; alterar apenas a referência documental não reabre etapas nem invalida cálculos se as entradas efectivas não mudarem. A alteração de localização, actividade ou outros pressupostos conserva os controlos de impacto existentes.
No simulador, o componente comum mostra as oportunidades, localizações e associações do dossier. Os campos recuperados continuam a usar os resumos editáveis da entrega anterior; respostas deliberadas do simulador não são substituídas. Relatórios e CSV do plano conservam cada oportunidade, o respectivo território e as associações escolhidas, com origem identificável, sem inferir incentivos ou autorização de execução.
Verificação do delta e regressão directamente afectada: `InvestmentPortfolioContinuityTest` e `InvestmentOpportunityChoicesTest`, seis testes/151 asserções em base QA isolada com transacções revertidas; incluem retoma após autenticação, selecção múltipla, associação partilhada entre fichas, preservação de título/localização/actividades, rejeição de origem não relacionada/privada, confirmação e recuperação no rascunho do simulador, e alteração só de referência sem invalidar cálculos. O teste de transporte dos 34 usa agora o mesmo agrupamento `selection_labels` do navegador, sem alargar os campos aceites pela API. Frontend: cinco testes territoriais/entrada e um teste de continuidade/CSV/impressão. Types e build aprovados. As provas dos incrementos anteriores mantêm-se; não se voltou a importar, publicar ou conferir os 20 artigos, 34 oportunidades, 408 limites ou 29 fichas na homologação.
Navegador QA: fixture identificada como SYNTHETIC QA, com oportunidade, âncora/associação e dossier próprios, sem atribuir a dados reais as associações do teste. Conferidos os parâmetros dos dois botões públicos, a recepção na entrada, escolha do dossier existente, rascunho guardado, prévia com Sofala/associação, confirmação e recuperação no simulador com a mesma referência. Os registos reais QA não foram reclassificados nem associados artificialmente para esta prova visual. Captura local `outputs/preservation-inventory-20261023/portfolio-dossier-simulator-continuity-QA.png`. Nenhuma escrita remota ou publicação real.
**Causa e prevenção da perda anterior:** mantém-se o diagnóstico documental de `e65cb269` e o manifesto nominal da reposição auditada. A mudança do importador para rascunhos deixou de reconciliar a divulgação anteriormente aprovada noutro ambiente; não se conhece a data exacta da perda num endereço. Nas próximas entregas, conservar a visibilidade/aprovação dos exemplares existentes e conferir os IDs/títulos/estados contra o inventário aceite, antes/depois do transporte; um transporte privado não revoga a divulgação anterior. Não restaurar a base inteira nem reimportar/publicar automaticamente conteúdos novos para corrigir visibilidade. Esta é prevenção técnica conforme a instrução de preservação da Tempo, não um novo aprovador. A presença visual não substitui o manifesto, a proveniência e o recibo de reposição existentes.
Saldo: visibilidade das 29 fichas confirmada no destino na passagem anterior; continuidade implementada neste recorte QA para associações efectivamente cadastradas. Fichas sem oportunidades associadas continuam a indicar essa lacuna; não foram preenchidas com relações presumidas. Homologação inalterada nesta passagem; aplicação transversal permanece parcial.
## Entrega QA reconciliada — preparação adaptativa, 23/10/2026
Conjunto funcional único, na ordem Git já existente: `7ca94967` → `f2150b19` → `5bda74ae`. A reconciliação documental `7d798579` conserva a evidência da carteira. Não fazer três instalações independentes, reaplicar patches ou substituir ficheiros por exemplares de cada candidata. Preservar estes commits e integrar o conjunto pelo processo Git de revisão existente.
| Candidata | Comportamento incluído | Provas aceites reutilizadas |
| --- | --- | --- |
| `7ca94967` | Ocultar preparação sem aplicação e respectivas acções; conservar estados e evidências; distinguir requisitos não aplicáveis nos relatórios | Teste API de preservação/reabertura: 18 asserções; relatório: 11 testes; types/build e cenário QA |
| `f2150b19` | Dossier → simulador: resumos editáveis dos dados recuperados, nomes territoriais ligados aos IDs correctos, edições deliberadas preservadas, percurso de análise existente | Reutilização: 12 testes; nomes exactos: 14 asserções API; types/build e gravação/recuperação visual QA |
| `5bda74ae` | Situação do terreno acompanha Não/Sim/Por definir na interface, gravação/validação, recuperação e relatórios; resposta anterior conservada | Delta de terreno: 4 testes frontend e 32 asserções API; types/build e três estados no navegador QA |
**Reconciliação Git:** base `origin/main` consultada novamente no remoto: `e1f010f941e9993645e131b34ec1f4c3fe82c71b`. As três candidatas são antecessoras do conjunto reconciliado; não há commits exclusivos de `main` a resolver nesta conferência. Os quatro worktrees existentes estavam limpos e os dois worktrees de outras entregas permaneceram intocados. Conferido o delta acumulado sem erros de whitespace; sem migrations, novas rotas ou alterações de dependências. Não foi reescrito histórico nem repetida a suite QA aceite. Esta conferência de integração não é nova prova funcional de todo o portal.
**Disponibilização e reversão:** esta entrega permanece em QA, sem integração em `main` ou deploy nesta passagem. Instalação futura usa o build e o deploy Git existentes; não requer importação, migração de dados, publicação de conteúdos ou novos papéis. Reversão de código deve conservar o payload cifrado, decisões, respostas anteriores, referências, relatórios emitidos e histórico; não restaurar uma base antiga nem eliminar campos/registos para reverter a interface. Conferir apenas o delta autorizado no destino quando a entrega for integrada.
**Formulação do saldo:** Homologação inalterada por esta entrega; aplicação transversal ainda parcial. A carteira e os projectos âncora permanecem prioritários no mesmo plano de estabilização. A visibilidade das 29 fichas foi confirmada contra o manifesto aprovado; a continuidade carteira → oportunidade → dossier → simulador permanece por concluir. Não declarar «sem reposição confirmada» para a visibilidade, pois isso contradiz a evidência registada e a conferência de leitura de `7d798579`. Não considerar encerrada a estabilização integrada por causa dessa visibilidade ou dos incrementos do simulador.
Pendência concreta preservada na matriz: transportar a origem `from_dossier` ao escolher Preparar investimento, sem presumir associações inexistentes. Nenhuma correcção dessa lacuna foi incluída silenciosamente neste conjunto.
## Carteira no percurso integrado — reconciliação do saldo, 23/10/2026
Decisão expressa da Tempo: carteira/projectos âncora e percurso adaptativo têm diagnósticos distintos, mas a separação não significa adiamento nem exclusão do percurso. A experiência deve conservar o contexto em corredor/localização → oportunidades → associações documentadas a âncoras e clusters → dossier → simulador. A recuperação da visibilidade é prioritária até confirmação da causa e da reposição; os incrementos adaptativos não constituem prova dessa reposição.
**Correcção do saldo comunicado nos últimos incrementos:** a causa e a reposição já estavam registadas na secção Preservação do concluído e no recibo existente. O importador passou a criar rascunhos sem reconciliar a aprovação documental anterior; o delta de `e65cb269` em `ImportInvestmentPortfolio.php` confirma a retirada de `--approval` e do caminho de publicação previamente aprovado. Não se atribui a essa alteração a data exacta de desaparecimento num endereço. A reposição anterior foi nominal, auditada e preservou relações/conteúdo: 15 projectos, 8 clusters, 6 corredores.
**Conferência actual de leitura na homologação:** a rota pública `/carteira` apresenta 29 fichas. Os 29 identificadores e títulos coincidem integralmente com `approved-portfolio-visibility-20261023.json`: zero ausentes e zero títulos divergentes. Contagem apresentada: 15 projectos, 8 clusters, 6 corredores. A ficha Cadeia de Valor do Arroz por Projecto de Processamento abre em `/carteira/57a4ee7a-4c07-4e26-b8f5-e07726e41512`. A sessão do navegador estava autenticada, mas foi consultada a rota pública, cujo controlador aplica a mesma consulta `visible()`; a prova anónima da reposição anterior mantém-se no recibo. Não foram executadas novas publicações, importações, escritas remotas ou reposições de dados nesta conferência.
**Continuidade ainda por concluir, distinta de desaparecimento:** a ficha de arroz consultada informa não ter oportunidades publicadas associadas. Não inferir ligações por semelhança de corredor ou cadeia de valor. No código actual, a carteira transmite `from_dossier` para uma oportunidade e a descoberta valida a origem; a ligação Preparar investimento em `investment/detail.tsx` transmite apenas `source=opportunity:{id}`. Falta preservar explicitamente essa origem documental no arranque/contexto privado. A presença das 29 fichas não demonstra essa continuidade até ao simulador, nem encerra a aplicação transversal. Manter este ponto na matriz existente e reaproveitar o percurso, sem criar cadastros paralelos ou novas associações presumidas.
## Situação do terreno acompanha a necessidade de terra — delta QA, 23/10/2026
Origem: decisão expressa da Tempo após `f2150b19`. No simulador ligado ao mesmo dossier, **Não** oculta Situação do terreno sem a exigir; **Sim** apresenta o campo com o valor já guardado e permite alterá-lo; **Por definir** identifica a pendência e permite guardar sem resposta. O simulador autónomo conserva o comportamento anterior. Não se infere a necessidade de terra da localização, do corredor ou da escolha de um documento.
A decisão vem de `investor_path.facts.land_needed`, já existente. O contexto de preparação transporta-a na referência guardada do simulador; versões anteriores recuperam a aplicabilidade S10. Informação ausente ou inválida permanece por definir. Arrendamento/DUAT já declarados no dossier podem preencher a situação correspondente quando a necessidade é Sim, sem sobrescrever uma edição deliberada no simulador. A área das instalações e os requisitos documentais/jurídicos não são alterados por inferência.
Gravação e validação: Situação do terreno continua opcional, sem criar obrigatoriedade para Sim nem para Por definir. Quando fornecida, conserva a validação das opções existentes. Na actualização do rascunho, o servidor consulta o dossier real vinculado ao seu titular, mesmo sem o parâmetro `context`, e conserva o valor e o nome anteriores enquanto o campo estiver oculto. A decisão guardada na referência é sincronizada com esse dossier; uma referência antiga não muda a decisão real. Recuperar Sim volta a mostrar o mesmo valor, editável. Valores anteriores permanecem no rascunho cifrado; fontes, decisões, resultados históricos e relatórios já emitidos não são apagados ou reescritos. Sem migration e sem alteração na homologação.
Relatórios e CSV do plano usam a mesma decisão: Sim apresenta a situação declarada ou a informação em falta; Não apresenta a explicação de não aplicabilidade à preparação actual, sem apresentar a resposta histórica como actual; Por definir apresenta a pendência. O modelo comum alimenta Word, impressão/PDF e restantes exportações do relatório. Os dados originais do rascunho permanecem conservados.
Verificação apenas do delta: quatro testes `investment-land-preparation.test.mjs` (três estados, recuperação, versão anterior, referência inválida, validação, CSV e impressão), um teste API em base QA isolada com 32 asserções (gravação incompleta, decisão real, omissão do campo, recuperação e alteração após Sim); types/build aprovados. No navegador, exclusivamente no dossier sintético QA: Não ocultou o campo; Sim permitiu guardar DUAT; Por definir mostrou a pendência e permitiu guardar; regressar a Sim recuperou DUAT seleccionado e editável. A cobertura transversal continua parcial. O saldo actual da carteira e da continuidade integrada encontra-se na reconciliação acima; este incremento não demonstra a reposição.
## Continuidade oportunidade → localização → dossier → simulador — incremento QA, 23/10/2026
Origem: decisão expressa da Tempo de aperfeiçoar o percurso adaptativo por incrementos. Mantém-se `7ca94967` como primeiro recorte QA; nenhuma destas duas alterações foi instalada na homologação nesta passagem. Diagnóstico de visibilidade e continuidade integrada da carteira: consultar a reconciliação acima; não os considerar adiados ou fora do percurso.
| Ponto encontrado | Delta neste incremento | Limite preservado |
| --- | --- | --- |
| Corredor/localização já escolhidos reapareciam no simulador como perguntas abertas | Reutilizar os mesmos identificadores e nomes do cadastro; mostrar resumos editáveis para valores herdados sem alteração local | Não inventar posto/localidade; manter as localizações específicas de todas as fichas. O simulador conserva o contexto geral da primeira escolha, sem agregar cálculos de oportunidades diferentes |
| Título, descrição e sector recuperados voltavam a pedir preenchimento | Resumo do valor recuperado e opção Alterar; falta de dado, erro de validação ou edição deliberada mantém a pergunta aberta | A gravação existente, as versões e a validação no servidor continuam a aplicar-se; não aceitar dados apenas porque o resumo foi mostrado |
| Entrar no simulador desde o dossier pedia novamente o tipo de percurso/finalidade | Entrada nova selecciona o percurso existente Avaliar o meu investimento / Apenas simular; opção Alterar disponível | Escolha técnica de navegação, não declaração jurídica. Não substituir uma finalidade previamente escolhida pelo utilizador; não escolher detalhe, perfil, regime fiscal ou respostas desconhecidas |
| Reutilização mostrava UUID, chaves e versões internas ao investidor | Componente comum apresenta nome do investimento, referências legíveis e regresso ao dossier | Identificadores, origem, versões e histórico permanecem nos registos; divergências não são resolvidas silenciosamente |
No simulador, os dados conhecidos aparecem como resumos com **Alterar**. Complete os restantes campos e use a gravação e o regresso ao dossier já existentes. Quando houver divergência, abra a comparação e seleccione expressamente os valores a substituir. Os nomes territoriais acompanham os identificadores correspondentes, incluindo após gravação/recuperação; uma escolha local divergente conserva o seu nome. Fontes sem nome recuperável não são mostradas como UUID no resumo.
Validação do delta: `context-inheritance.test.mjs` (12 testes, incluindo gravação/recuperação, edições/limpezas deliberadas, outro dossier, divergência geográfica e finalidade anterior); `InvestmentCorridorContextTest::test_simulator_reuses_names_for_the_exact_chosen_territory_and_sector_without_creating_a_location` (14 asserções, base QA isolada). A conferência visual utiliza exclusivamente o dossier sintético QA. A entrada pública/autenticação e a configuração de corredores reutilizam as provas aceites. Não se declara concluída a reutilização transversal em todos os consumidores; subsistem campos próprios do simulador e divergências a resolver pelo investidor, não dados a presumir.
Conferência visual concluída: entrada pelo mesmo dossier, percurso de análise seleccionado, título/resumo/sector como resumos editáveis, Sofala/Dondo recuperados, posto/localidade em falta sem preenchimento presumido. Uma edição deliberada do título no simulador sobreviveu à actualização da página; os restantes resumos continuaram herdados. Types e build passaram. A lacuna Situação do terreno identificada neste incremento foi tratada no delta QA descrito acima; o ocultamento de S10 em `7ca94967` não a tinha resolvido. Não alterar a aplicabilidade jurídica ou a área de instalações por inferência dessa resposta.
## Território adaptado ao corredor — decisão funcional da Tempo, 23/10/2026
Revisão funcional `0025309b` integrada pelo Git e disponível na homologação. Conferência do destino apenas de leitura: o serviço territorial para Maputo devolve exactamente as duas unidades existentes, sem consultar ou gravar um dossier real. A prova de recuperação e confirmação de Sofala/Dondo foi feita no dossier sintético QA, com indicação **Dados confirmados**. A prévia apresenta nomes e alterações úteis ao investidor, omitindo campos de identificadores internos.
Aplicada a tabela da Tempo como configuração do percurso existente (`config/investment_corridors.php`), usando os códigos e identificadores do cadastro territorial já aceite. Pemba–Lichinga: Cabo Delgado/Niassa; Nacala: Nampula; Vale do Zambeze: Tete/Zambézia; Beira: Sofala/Manica; Limpopo: Gaza; Maputo: **Maputo Província e Maputo Cidade**, conforme o esclarecimento final da Tempo. Só Nacala e Limpopo têm uma única província e a seleccionam automaticamente quando ainda não existe localização. Maputo exige escolher entre as duas unidades. Esta configuração funcional não declara novos limites geográficos nem altera os mestres, as associações ou as fichas publicadas.
Na preparação, cada oportunidade mantém a sua localização separada. O corredor inicial vem da fonte da ficha quando disponível; o investidor pode escolher outro corredor para o seu investimento, sem alterar a associação publicada da oportunidade. Uma mudança conserva o caminho ainda compatível; se a província deixar de pertencer ao corredor, retira os níveis territoriais dependentes e explica a alteração. A província única é proposta automaticamente; um território documentado ou previamente confirmado continua protegido pela confirmação de alternativa aplicável. A localização da primeira oportunidade alimenta os campos gerais existentes; outras escolhas permanecem identificadas separadamente.
Zonas e parques deste percurso só aparecem com território público documentado compatível, reutilizando a visibilidade, validade e vínculos existentes. Influência/proximidade não equivale a implantação. Uma indicação provincial de localização não é inventada como cobertura de todos os distritos; apenas âmbito legal documentado permite esse alargamento. Ausência de opções é distinta de erro de consulta, com nova tentativa. Ao mudar a localização, um parque que deixe de ser compatível é retirado e a razão aparece no formulário. Consulta não confirma lotes disponíveis, admissão ou benefícios; nenhum cálculo é activado.
Recuperação: o rascunho do formulário é guardado no payload cifrado do mesmo dossier, por actor, sem confirmar contexto, aumentar a versão funcional ou activar cálculos. Recupera escolhas, actividades e dados ao regressar, actualizar ou autenticar-se novamente. A gravação automática tem indicação de progresso/sucesso/erro e nova tentativa. Pedidos atrasados não substituem rascunhos mais recentes; conflito com alteração do dossier conserva o rascunho para revisão. Retirar uma ficha não a faz reaparecer ao actualizar a mesma entrada; referências novas vindas do catálogo continuam a ser acrescentadas. A confirmação normal guarda o contexto, preserva o histórico e retira o rascunho desse actor. Não há nova tabela, migration, papel, aprovador ou alteração da autorização existente.
Validação no servidor: recusa território fora do corredor tanto na prévia/confirmação como no rascunho; recusa parques sem compatibilidade documentada. Os filtros da API territorial aplicam a mesma configuração. A gravação protege o dossier e o actor, com controlo de concorrência, sem aceitar combinações só porque foram enviadas directamente à API.
Provas QA: suite de cinco testes de configuração, validação, recuperação/autenticação, conflitos, zonas e mudança de corredor — **90 asserções**; quatro testes de continuidade de oportunidades também passaram na passagem completa anterior. Testes usam exclusivamente `simulator_fiscal_delta_20261021_test:55441`, com transacções revertidas. Tipos e build passaram. Navegador com conta/dossier **sintéticos privados em QA**: Beira oferece Sofala/Manica; Nacala selecciona Nampula; Maputo oferece as duas unidades; Sofala/Dondo recuperado depois de actualizar. Captura: `outputs/preservation-inventory-20261023/corridor-adaptive-territory-QA.png`. Nenhum dossier real foi usado para provas de gravação. As 34 oportunidades, Turismo de Inhambane, permissões e geometrias não foram alterados por esta implementação.
## Continuidade entre escolha e preparação — 23/10/2026
Falha reportada pela Tempo: depois de criar o dossier para Florestas/Beira, o editor começava por uma avaliação do estado ainda não confirmado (`contexto v0`). A escolha solicitada vinha separadamente em `source`/`requestedChoices`; não era outro investimento, mas a avaliação do estado guardado vazio. A ligação do quadro voltava ao editor sem os parâmetros da escolha pendente, permitindo perder essa referência antes de confirmar. Não foi identificada nem corrigida por escrita uma troca de dados entre dossiers.
O editor passa a começar por **Preparar investimento**, pela oportunidade e pela localização por ficha. Retirado o quadro de avaliação do estado guardado deste ecrã de edição, incluindo a ligação que descartava a escolha pendente. Requisitos guardados só são apresentados quando correspondem às escolhas que estão no formulário. Botões coerentes: **Criar dossier e preparar investimento**, **Rever dados antes de guardar**, **Guardar dados do investimento**. A confirmação continua necessária para guardar alterações; abrir uma referência não altera o dossier.
Nos consumidores de avaliação, retirados números internos de contexto/versão, explicações de implementação, quadro genérico de ausência de zona e elementos sem título na lista de etapas. Mantidas condições úteis, fontes oficiais e limites efectivos do simulador. O exemplo real foi recuperado por GET com `source=opportunity:53` no dossier indicado pela Tempo, sem guardar ou criar dados. Teste QA isolado: 20 asserções sobre recuperação da escolha e igualdade do contexto antes/depois da abertura. Tipos e build conferidos; sem migrations ou alterações de cálculo, autorização ou dados activos.
Revisão funcional `72262875` integrada pelo Git e conferida no navegador da homologação: título **Preparar investimento**, Florestas/Beira identificada, localização por escolher e botão **Rever dados antes de guardar**; o quadro de contexto v0 já não aparece. Mantido aberto o dossier existente com a referência solicitada recuperada, sem confirmar alterações. Captura: `outputs/preservation-inventory-20261023/investment-preparation-continuity-homologation.png`.
## Dossier orientado ao investidor — 23/10/2026
Correcção da apresentação solicitada pela Tempo: o componente partilhado de documentos do contexto mostrava, por defeito, identidade técnica, versões, hashes, notas de permissões e mensagens de ausência de suporte. Era reutilizado na entrada, edição e consulta do dossier; a entrada repetia ainda a mesma oportunidade num bloco de origem técnica.
O investidor vê agora a escolha pelo título e respectiva ligação. **Documentos de apoio** só aparece quando existem documentos, com título, edição disponível, referência de secção e consulta do documento. Sem documentos, não se apresenta uma caixa vazia nem uma pendência genérica da biblioteca. Retirados também UUID, motor de cálculo, versões internas e chaves de origem dos resumos, prévia e consulta do dossier. Mantidas as informações operacionais relevantes: escolhas, localização, impactos da alteração, resultados que precisam de revisão, documentos e pedidos identificados por protocolo. As rotas e verificações de acesso são as mesmas; não há mudança de dados, histórico, fontes, permissões, cálculos ou publicações.
Tipos e build passaram. Revisão funcional `98474777` integrada pelo Git. Reproduzido o exemplo Florestas/Beira (`opportunity:53`) na homologação antes e depois da integração, sem criar ou alterar um dossier: agora apresenta apenas **A sua escolha** com o título e ligação, sem caixa vazia de documentos, versões ou chave técnica. Captura: `outputs/preservation-inventory-20261023/investor-dossier-clean-presentation-homologation.png`. Esta é uma correcção de apresentação nos componentes e páginas do percurso, não uma declaração de revisão integral do portal.
## Explorar oportunidades por corredor — 23/10/2026
Disponível e conferido no navegador da homologação, revisão funcional `20349299`: [explorar Agronegócio por corredor](https://teste-investidor.tempo.co.mz/oportunidades?sector=agronegocio&view=corridors). Conferidos os seis grupos, 34 resultados, acesso no início de Agronegócio e escolha dos IDs 54/64 de corredores diferentes, mantida depois do filtro Soja. [Recibo da passagem](agribusiness-corridor-explorer-20261023.json).
Decisão expressa da Tempo: acrescentar o corredor como forma de explorar o catálogo, preservando vistas por sector, território e cadeia de valor, fichas únicas e percurso existente. Nova vista `/oportunidades?sector=agronegocio&view=corridors`, com acesso destacado no início de Agronegócio. Não há migration, cadastro duplicado ou alteração de publicações, associações, permissões, Turismo de Inhambane ou geometrias.
Cada grupo apresenta nome, número de oportunidades publicadas, localizações identificadas, cadeias e etapas/actividades. O lote usa as seis designações exactas da fonte aceite; não as funde com mestres semelhantes. Outras fichas usam as relações públicas já disponibilizadas pelo catálogo; proximidade não é tratada como pertença. Sem associação pública identificada, a ficha aparece em **Outras localizações**. Uma ficha em vários grupos mantém o mesmo ID e a selecção é única. Contagens de grupos podem sobrepor-se; não significam cadastros distintos.
Dentro do grupo, filtrar província, distrito e cadeia de valor; filtros mantêm escolhas anteriores, inclusive de outros grupos. **Comparar as escolhidas** reutiliza a comparação existente de até quatro fichas; essa capacidade não limita a escolha para preparar investimento. **Preparar investimento com as oportunidades escolhidas** envia todos os IDs ao mesmo dossier, onde se confirma a localização por ficha e se reutilizam os dados em licenciamento, terra e simulação. Filtrar a pesquisa não altera a localização da ficha nem escolhe silenciosamente uma implantação.
Lacunas reais: as 34 fichas não têm província/distrito de implantação identificados; a vista indica abrangência por indicar e não transforma nomes de corredores em limites territoriais. As actividades produtivas são apresentadas com o texto da fonte, sem inventar uma classificação de etapa. Etapas adicionais vêm apenas das relações públicas existentes. Os originais e a rastreabilidade permanecem internos.
Delta conferido: dois testes, 26 asserções (seis grupos e 34 IDs sem truncar à primeira página; relação múltipla com um ID; grupo residual; localizações conhecidas sem inventar faltantes). Build e tipos verificados. Navegador QA: escolha de duas fichas de Beira e Maputo, filtro Soja mantendo as escolhas e comparação das duas identidades. Reutilizadas as provas aceites da confirmação territorial e continuidade do dossier.
## Apresentação sem notas internas — 23/10/2026
Correcção solicitada pela Tempo: a interface deve comunicar a tarefa e o conteúdo, sem narrar decisões de implementação. Simplificados o cabeçalho da gestão do lote, a entrada no roteiro e as escolhas de oportunidades. Na ficha pública, removido o alerta genérico de falta de data de validação; mantém-se o estado **Em preparação** e a indicação de que a execução depende das condições e autorizações aplicáveis.
A apresentação das 34 fichas elimina exclusivamente os parágrafos automáticos conhecidos de nome de ficheiro, hash e metadados vazios. Conserva a actividade, o corredor e a informação útil sobre definição de localização e condições. A mesma apresentação é usada na serialização HTML/API e na prévia. Não há alteração de dados, fontes, versões, permissões, auditoria ou publicação; textos posteriormente revistos pela equipa são preservados. As notas e originais continuam no cadastro interno. Esta correcção não declara revista a totalidade das páginas do portal.
Verificação: teste novo de apresentação/preservação (7 asserções), quatro testes do ciclo promocional e controlo de acesso passaram; tipos e build passaram; ficha conferida no navegador QA. A execução alargada encontrou três testes históricos incompatíveis com o estado actual da fixture (lote já divulgado e directório de contas excluído da candidata anterior); não são declarados aprovados nesta passagem nem foram alterados para ocultar essas diferenças.
## Orientação do lote de Agronegócio — catálogo e selecção múltipla, 23/10/2026
**Homologacao concluida, 23/10/2026:** 34/34 fichas cadastradas e divulgadas como **Em preparação**, pela interface da conta real, com previa, motivo, confirmacao e MFA recente. Revisao servida `e12ef6ad426ff6ebc4e7421a1ddbe7f184d77a55` (funcional `c641b267`), integrada pelo Git, sem migration. [Catalogo publico](https://teste-investidor.tempo.co.mz/oportunidades?sector=agronegocio), [gestao unica](https://teste-investidor.tempo.co.mz/gestao-conteudos) e [gestao do lote](https://teste-investidor.tempo.co.mz/minhas-oportunidades/lotes/agronegocio). A divulgacao nao autoriza execucao. [Recibo final](agribusiness-homologation-20261023.json).
**Delta final:** IDs 31-64, sem duplicados; 34 resultados e escolha de duas fichas, IDs 64 e 63, no percurso existente. As 30 oportunidades anteriores, incluindo Turismo de Inhambane, permanecem exactamente iguais ao backup. Das 174 tabelas, 170 mantem checksum integral; alteracoes apenas em oportunidades (+34), auditoria (+102 eventos), cache e sessoes. Biblioteca (59 recursos), 408 geometrias, permissoes e simulacoes preservadas. Backup/restauro isolado verificado antes da operacao. Cadastro, edicao, recuperacao, previa, divulgacao, actualizacao e retirada usam o portal, sem comandos ou Codex nas operacoes correntes. Reutilizadas as provas QA do percurso, regras e historico; sem dossiers ficticios na homologacao.
**Preservação conferida antes do cadastro:** 172/174 tabelas iguais ao backup; apenas cache e sessões diferem. As 30 oportunidades preexistentes, biblioteca com 59 recursos, 408 geometrias, permissões e tabelas de simulação permanecem preservadas. A primeira comparação da aplicação, sem contexto de actor, mostrava vazias as tabelas de simulação sujeitas a RLS e originou um alerta incorrecto. A comparação posterior usou a mesma identidade de leitura do backup e confirmou igualdade integral dessas tabelas. Nenhuma reposição, eliminação ou alteração de política foi executada; a pausa técnica do observador foi desfeita e o deploy está activo.
Decisão expressa da Tempo nesta passagem: publicar as 34 fichas no catálogo de Agronegócio e permitir escolher uma ou várias, confirmar a localização por ficha, preparar o investimento com reutilização dos dados e consultar requisitos/documentos/entidades, simular e acompanhar no dossier autenticado. Corredores, clusters e projectos âncora enriquecem a análise e não são pré-condição de publicação; localização não activa benefícios fiscais. Não alterar Turismo de Inhambane. Esta orientação supera o estado privado do recibo QA anterior, conservado como histórico.
**Conferência do existente e delta:** o catálogo já comparava quatro fichas e o dossier já tinha 19 etapas, prévia/confirmação de contexto, mapas, reutilização no simulador/terra/Balcão, pedidos e histórico. Faltava transportar uma escolha múltipla identificada para esse mesmo dossier. Acrescentado `opportunity_choices` no contexto cifrado existente, sem tabela, migration, novo dossier paralelo ou novo papel. Cada escolha conserva ID, título, versão/hash, fonte, corredor documental e referência territorial própria. A escolha do catálogo persiste ao mudar de página; ao continuar um dossier, referências novas são acrescentadas às existentes, sem eliminar silenciosamente as anteriores. A comparação de quatro fichas permanece independente da escolha de investimento. A guarda de 100 referências por pedido é uma escolha técnica de tamanho, não nova decisão organizacional.
**Percurso da equipa e do interessado:**
1. Na gestão única, abrir **Rever o lote das 34 oportunidades de Agronegócio** (`/minhas-oportunidades/lotes/agronegocio`). Conferir o manifesto e cadastrar/recuperar privadamente. Depois, preparar a prévia dos 34 textos, rever por ficha e confirmar a divulgação com motivo e MFA recente. A publicação não depende de relações com mestres. Edição, actualização ou retirada individual continuam na divulgação promocional existente.
2. No catálogo, marcar **Escolher para preparar investimento** em uma ou várias fichas e usar **Preparar investimento com as oportunidades escolhidas**. Entrar normalmente, continuar um dossier autorizado ou criar um novo. Confirmar cada ficha e a localização no editor do mesmo contexto.
3. Localização documentada é recuperada. Uma alternativa exige confirmação explícita e motivo; a ficha de origem fica intacta. Uma alternativa já confirmada não exige repetir a decisão enquanto se mantiver. Sem informação de implantação, escolher o nível territorial conhecido ou conservar a lacuna identificada; o mapa não inventa limite, parcela ou DUAT.
4. Declarar as actividades efectivas por ficha. A prévia e o dossier apresentam requisitos por oportunidade: localização/geolocalização, acesso à terra/DUAT, ambiente, enquadramento sectorial e ramos industrial/comercial quando declarados, com fundamento, documentos, entidade e ligação à etapa existente. Reutilizadas as condições e fontes da preparação do investimento privado. Fontes adicionais de consulta: [BAU — legislação](https://www.bau.gov.mz/legislacao/), [serviços BAU](https://www.bau.gov.mz/nossos-servicos/) e [quadro ambiental oficial](https://sibmoz.gov.mz/legal-framework/). Categoria ambiental, modalidade de licença e competência concreta dependem de actividade, dimensão e local; não são deduzidas de um corredor. Água, florestas, pecuária e transporte mantêm visível a necessidade do procedimento sectorial específico quando não identificado no acervo aplicado. Não é nova validação genérica da fonte nem dispensa de licença.
5. Confirmar a prévia. Título próprio e dados declarados são preservados; resumo/sector/local da referência principal são reutilizados. As outras fichas e localizações mantêm-se individualizadas: não se somam investimentos ou benefícios presumidos. Abrir as ferramentas e o acompanhamento pelas ligações do roteiro. O snapshot da simulação conserva o conjunto de escolhas, fontes e requisitos; a guarda textual foi aumentada de 20.000 para 1.000.000 caracteres para comportar o lote, sem mudança de schema. Mudança de actividade/local/escolha identifica resultados para revisão e preserva os valores e o histórico anteriores.
**Provas do delta QA:** sete testes específicos/de continuidade, 132 asserções; selecção múltipla, localizações distintas, recusa de alteração territorial silenciosa, não repetição da confirmação já aceite, recusa de fonte alterada após prévia, escopo de acesso, regras por actividade, histórico/resultados preservados e guarda das 34 escolhas no simulador. Prova adicional da operação de lote pela sessão HTTP normal: um teste, 15 asserções, incluindo manifesto divergente, prévia exacta de 34, repetição idempotente, recusa sem MFA/âmbito e preservação das restantes oportunidades. As 34 fichas foram efectivamente divulgadas **em QA**, como Em preparação; a simulação inicial de papéis QA não constitui autenticação real de homologação. Conferidos também no navegador 34 resultados e escolha de duas fichas com acção de preparação. Não repetidas as suites gerais aceites.
**Disponibilização no destino:** revisão servida inicial `761035ae`; 30 oportunidades preexistentes preservadas no inventário. Conta real de cadastro e organização operadora localizadas pelas designações existentes, sem criar papéis ou contas. Backup/restauro fresco em `/var/backups/investmoz-agribusiness-opportunities-20261023/backup`: 174 tabelas e 369 ficheiros conferidos. A integração Git de código é separada do cadastro/divulgação autenticados pela página do lote. Sem migrations ou importação de ficheiros privados; os originais e artigos existentes permanecem no armazenamento. Reversão de visibilidade usa retirada auditada pela interface, sem restaurar a base nem apagar manifestações/dossiers posteriores; reversão de código deve preservar o novo contexto cifrado e as referências adicionadas. A conferência final do destino e o recibo devem ser registados abaixo após execução.
## Correcção de âmbito — 34 oportunidades cadastradas e roteiro em QA, 23/10/2026
A lista informativa anteriormente instalada no artigo não cumpria o cadastro pedido pela Tempo. Esta correcção cria **34 fichas privadas reais na base QA isolada**, recuperáveis pela gestão existente, com título, resumo e descrição da entrada, sector Agronegócio, nome exacto do corredor documental, fonte e hash do DOCX. Conserva as seis secções e os totais 5/6/6/7/5/5. Não modifica a homologação, os corredores mestres, dossiers anteriores ou o original do artigo. Não transforma a organização operadora no promotor dos investimentos. Implantação, área, montantes, promotores, viabilidade e licenças ausentes ficam explicitamente por completar; nenhuma correspondência com corredores de nomes mais amplos foi inferida.
Reutilizada a implementação QA de divulgação promocional da candidata `9d3bfa89`, limitada ao fluxo funcional necessário. Não transportadas as alterações independentes da administração de contas. A prévia, divulgação, actualização e retirada usam as designações, âmbito e MFA existentes, sem novos papéis ou aprovadores. O resumo público é seleccionado explicitamente; preparação, contactos e documentos privados não são copiados automaticamente. Divulgação identifica **Em preparação** e não autoriza execução. Publicação documental e aprovação/publicação de uma ficha completa continuam operações distintas.
**Guia curto da equipa em QA:**
1. Abrir a [gestão única QA](http://127.0.0.1:18574/gestao-conteudos), entrar normalmente com a conta designada e abrir [Minhas oportunidades](http://127.0.0.1:18574/minhas-oportunidades). Pesquisar o título ou corredor; as fichas iniciais são privadas.
2. Abrir **Editar**, completar/corrigir os dados disponíveis, guardar e reabrir. A origem documental aparece separadamente; seleccionar localização apenas quando houver informação suficiente. A referência textual de corredor não é uma parcela.
3. Abrir **Divulgação promocional**, rever o texto público e a fonte, preparar a prévia e confirmar com motivo e MFA recente. A prévia conserva as lacunas e o estado **Em preparação**. Alterar a ficha privada não copia automaticamente dados para o resumo público: rever a selecção na divulgação.
4. Depois de divulgar, a entrada correspondente no artigo aponta para a ficha individual. Nela, o interessado pode preparar uma manifestação de interesse ou escolher **Iniciar ou continuar o roteiro deste investimento**. O percurso existente permite seleccionar um dossier ou criar um novo, comparar e confirmar a referência; preserva o título que o interessado já deu ao seu investimento.
5. Para corrigir o resumo divulgado ou retirá-lo, usar a mesma área de divulgação, com prévia e auditoria. A retirada remove a consulta pública e a ligação do artigo, preservando cadastro, preparação e histórico.
**Provas do delta:** dois testes sobre as 34 fichas, 534 asserções: abrir/editar, prévia, divulgação, ficha pública, origem de roteiro, correspondência das seis listas, actualização/retirada e repetição sem duplicados nem perda da edição. Teste adicional de interessado: 19 asserções, criação de dossier, comparação/confirmação da oportunidade, recuperação de título próprio/resumo/sector, abertura do roteiro e preparação da manifestação. Escritas desses testes revertidas por transacção: as **34 fichas permanecem privadas**, não divulgadas como efeito do cadastro. Conferidos por comparação os registos preexistentes de oportunidades/auditoria, corredores, permissões, biblioteca e documentos. Verificação de tipos e build passaram. Trata-se de prova HTTP dos percursos usados pela interface, sem afirmar uma nova sessão manual de navegador ou repetir as suites gerais aceites.
Carregamento inicial executado uma vez por `tools/register-corridor-agribusiness-qa.php`, com guarda exacta de base/porta QA, actor e organização já designados, snapshot privado e chaves estáveis por entrada. A repetição recupera os mesmos IDs sem sobrescrever edições posteriores ou reactivar uma retirada. Esse carregamento técnico de instalação não é uma operação corrente da equipa. **Editar, guardar/recuperar, pré-visualizar, divulgar, actualizar, retirar e iniciar/continuar o roteiro não exigem comandos nem Codex.** Não há migration ou nova dependência. Disponibilização na homologação e transporte das 34 fichas continuam separados desta entrega QA; não foram executados nem autorizados por esta correcção. [Recibo QA](agribusiness-opportunities-qa-20261023.json).
## Agronegócio nos corredores — complemento de apresentação, 23/10/2026
Entrega QA recebida em `entrega-agronegocio-corredores-20261023`: cinco ficheiros conferidos pelos hashes do manifesto. Complementa exclusivamente `/agronegocio`, nas seis secções existentes: Pemba–Lichinga (5), Nacala (6), Vale do Zambeze (6), Beira (7), Limpopo (5) e Maputo (5), total 34 entradas. Reutilizadas as provas QA entregues (dois testes, 63 asserções e verificador offline do exemplar preservado); não constituem prova HTTP do destino.
Delta: serviço `CorridorAgribusiness`, fonte JSON com hash do DOCX e chamada adicional em `LegacyContent::article`. O ficheiro anterior de `LegacyContent` na homologação corresponde exactamente à base local; não há alterações posteriores a sobrescrever. Sem migration, importação, escrita de conteúdo, publicação de dossiers ou alteração dos nomes/cadastros de corredores. Originais e versões da biblioteca permanecem preservados; estas entradas são conteúdo informativo do artigo, não 34 novos dossiers executáveis.
Disponibilização pelo Git e mecanismo automático existente, com cópia privada do código/recibo anteriores em `/var/backups/investmoz-agronegocio-corredores-20261023`. Backup/restauro de dados já verificados nesta sessão em `/var/backups/investmoz-preservation-inventory-20261023/backup`; esta entrega não escreve na base. Reversão delimitada: retirar apenas a chamada de complemento, preservando alterações posteriores; não restaurar a base de dados. Conferir apenas o delta HTTP após a promoção da revisão.
**Instalação concluída na homologação:** revisão funcional `20a4099d10400496e19f03589b6383f512b48d9d`, promoção automática terminada às 09h57 de 23/10/2026 (África/Johannesburgo). [Página pública](https://teste-investidor.tempo.co.mz/agronegocio) respondeu HTTP 200 com as seis listas e 34 entradas exactas. Remover apenas os acrescentos recupera exactamente o HTML anterior; imagem, metadados, origem, versão e hash do artigo permanecem iguais. Recibo do pipeline: zero migrations e zero importações. [Recibo do delta HTTP](agronegocio-corredores-installation-20261023.json). Nenhum dossier ou corredor foi criado, renomeado, fundido ou publicado por esta instalação.
### Registo da prova QA anterior — agronegócio nos seis corredores
**Registo histórico: a pendência de instalação indicada nesta prova foi resolvida pela conferência HTTP acima.**
Fonte fornecida pela Tempo: `investimentos nos corredores.docx`, SHA-256 `1473b06c48aa234e771660e3f2a51b9d2e4e1943bd52d878e0ae854ee6657055`. Extraídas 34 entradas, conservando o texto das oportunidades e as seis designações do documento: Pemba–Lichinga (5), Nacala (6), Vale do Zambeze (6), Beira (7), Limpopo (5) e Maputo (5). Preparação em `database/data/corridor-investment-opportunities.json`; os rótulos de cadeia servem apenas para organizar as entradas. Âmbito confirmado pela Tempo: **agronegócio**; outros sectores serão acrescentados quando a informação estiver disponível.
**Preservar os nomes dos corredores actualmente cadastrados, os dossiers, relações, aprovações e informações anteriores.** Retiradas as associações inicialmente inferidas entre os seis nomes do documento e os quatro dossiers de âmbito mais amplo; a apresentação experimental na carteira foi retirada. A Tempo confirmou expressamente as seis designações separadas. Foram localizadas, com essas designações exactas e as letras a)–f), nas secções do artigo existente `/agronegocio`. A integração acrescenta a cada secção a sua lista de oportunidades logo após o parágrafo de cadeias prioritárias; a fonte do complemento é identificada em cada lista. Não renomeia, funde ou cria corredores, não altera os dossiers da carteira e não preenche outros sectores.
`App\Services\CorridorAgribusiness` complementa apenas a apresentação do artigo de agronegócio a partir do JSON; o exemplar original e a sua versão/hash permanecem intactos. Só corresponde a nomes exactos, seguidos pelo parágrafo existente de cadeias prioritárias. Não infere relações, limites, geometrias, projectos ou oportunidades cadastradas a partir de semelhança de nomes. A operação é idempotente e não escreve na base de dados. Artigos de outros sectores continuam no percurso anterior.
Conferência do delta: dois testes unitários, 63 asserções, incluindo separação de listas, nomes e texto anterior preservados, repetição sem duplicação e recusa de nomes de âmbito mais amplo. Verificador offline `tools/verify-corridor-agribusiness.php` executado sobre o exemplar real de Agronegócio, SHA-256 `0929184a6d4645b9046512675394ad4f60575273f6dab0955f71cc7474a6a4e2`: seis listas, 34 entradas, original inalterado e recuperação exacta da apresentação anterior ao remover apenas os acrescentos. A leitura do artigo pela biblioteca desta base QA devolveu 404; por isso, esta prova é sobre o exemplar real preservado e não demonstra consulta HTTP da biblioteca nem instalação na homologação. Sem suite geral repetida, migrations, publicação ou alteração remota. Para instalar basta o delta PHP/JSON; não há alteração de frontend ou necessidade de nova compilação. Estado: implementação QA concluída; disponibilização na homologação por executar.
## Preservação do concluído — inventário e reposição, 23/10/2026
**Prioridade expressa da Tempo:** inventariar o que já estava concluído, identificar o que desapareceu e repor, antes de avançar com outras rondas de testes/validações. Actualizado o [inventário existente dos 43 componentes](#saldo-dos-43-componentes) com uma coluna de saldo actual; conservada também a matriz executável dos 17 percursos. Os componentes de interface ali referenciados existem no código actual. As entregas posteriores prevalecem sobre estados históricos: por exemplo, os incrementos 36–39 já não podem ser apresentados como ausentes, e o acervo editorial já não está genericamente desabilitado. Não se criou outra matriz/backlog nem se usou o inventário para exigir novamente aceitações anteriores.
**Recortes concluídos a preservar**, com os limites e recibos das secções próprias: gestão única e editorial documental; 20 artigos públicos e 28 rascunhos privados; sectores/Fintech e cartões educativos; navegação contextual e percurso adaptativo privado; recortes entregues de parques; cadastro e recursos das oportunidades; divulgação dos 30 resumos Inhambane em preparação; perfis/login/MFA; demarcação recuperada; recortes de tesouraria/exportações e consumidor IVA já entregues; instalação de Concursos e anúncios; e carteira documental agora reposta. A presença de código não encerra os critérios parciais de educação, integrações externas, participantes reais ou variantes fiscais. Não se criam dados de exemplo para preencher catálogos vazios, nem se transformam essas pendências em funcionalidades desaparecidas.
**Perdas e reposições identificadas:**
- **Carteira, clusters, corredores e projectos âncora:** 29 dossiers existiam em QA e homologação, todos em rascunho, enquanto os exemplares anteriores estavam publicados com aprovação `document_owner`. Reposta a visibilidade de **15 projectos, 8 clusters e 6 corredores**, sem nova importação. O catálogo público mostra novamente os 29 dossiers. Isto é divulgação documental informativa, não confirmação de execução, disponibilidade, financiamento ou promotor de cada projecto.
- **Demarcação territorial:** a diferença anterior de geometrias já foi reposta em 22/10; a leitura actual conserva **408 limites**. Não voltar a importar nem reabrir a prova aceite. As 26+147 correspondências pendentes e a inexistência de polígonos próprios de localidades permanecem lacunas reais, não novos desaparecimentos.
**Causa delimitada da carteira:** o importador actual cria os dossiers como rascunhos. O commit `e65cb26901a5ca18862134b20e4c17b1f1003ec2`, de 06/10/2026, retirou a opção `portfolio:import --approval` e o caminho que aplicava a aprovação documental do proprietário. A instalação noutro ambiente deixou de transportar/reconciliar essa decisão de divulgação. A leitura de 23/10 encontrou os 29 registos, 16 projectos, 8 clusters e 6 corredores preservados; não ocorreu eliminação desses mestres. Conteúdo, título e fonte dos 29 dossiers coincidem com os exemplares aprovados em 26/09 e conservados no snapshot de 07/10. Não há base para atribuir a um commit recente a data exacta em que desapareceram de um endereço específico.
**Reposição executada:** manifesto nominal de 29 originais, SHA-256 `8d07007a2e1e8758d4741b4ac594f78d5943f71ad78b2daae7f85aefa9d2de4a`; guarda contra exemplares arquivados, editados ou com versão/hash divergentes; reposição primeiro na base QA isolada e depois na homologação, pela sessão SSH autenticada. Mantidos IDs, conteúdos, fontes, mestres e relações. Apenas estado de divulgação, fundamento anterior, versão e data da reposição foram actualizados. **29 eventos auditados**, sem atribuir uma nova aprovação ou um revisor fictício; a evidência original da Tempo é conservada. `tools/restore-approved-portfolio-visibility.php` é uma operação de recuperação delimitada, não um importador que publica automaticamente novos conteúdos e não uma tarefa editorial corrente.
Antes da escrita activa: backup fresco SHA-256 `7b7a3a082e1259c53c321617eb84d10b544e5a211f5f708199026c4205d1c511`; arquivo de ficheiros/configuração `0cc28141a081ce9d097735c7815b46f55d99300adf7361d936b857a24f897b86`; restauro isolado de **174 tabelas**, com **369 ficheiros** conferidos. Depois: **171 tabelas inalteradas**; diferenças somente em dossiers, auditoria e sessões. Biblioteca/documentos, 20 artigos públicos, 28 privados, geometrias e atribuições de papéis preservados. O observador do deploy foi retomado. A ponte de acesso antiga tinha processos locais concorrentes; foram encerrados os clientes desta tarefa e usada uma única ligação autenticada. Não se registaram credenciais.
**Conferência limitada da reposição, sem suite geral:** consulta anónima `/carteira` com 29 dossiers, projectos com 15, clusters com 8 e corredores com 6, todos HTTP 200. A antiga mensagem de carteira vazia fica superada por esta reposição. Links: [carteira](https://teste-investidor.tempo.co.mz/carteira), [projectos âncora](https://teste-investidor.tempo.co.mz/projectos-ancora), [clusters](https://teste-investidor.tempo.co.mz/clusters), [corredores](https://teste-investidor.tempo.co.mz/corredores). Recibo/inventário: [preservation-inventory-20261023.json](preservation-inventory-20261023.json); manifesto de proveniência: [approved-portfolio-visibility-20261023.json](approved-portfolio-visibility-20261023.json). Não se repetiram as provas funcionais aceites de outros módulos.
**Preservação nas próximas entregas:** usar este inventário e a instrução de 23/10 em `AGENTS.md` como referência anterior à alteração. Conferir o delta de código, schema, dados, configuração e permissões, explicando qualquer redução de disponibilidade. Uma reposição não apaga edições posteriores nem revoga aprovações antigas por omissão. Reversão deve preservar as novas versões e a auditoria; não substituir toda a base por um backup anterior. O conjunto parcial da matriz continua parcial no seu âmbito, sem retirar o que já foi entregue.
## Guia curto — entrada única de gestão
**Link da equipa:** https://teste-investidor.tempo.co.mz/gestao-conteudos. Entre com a conta habitual e o seu MFA. As funções e âmbitos aprovados determinam os botões apresentados; não é necessário acesso à linha de comandos.
@@ -129,11 +401,19 @@ Relatório desta observação: [verificação editorial](../../../outputs/invest
## Matriz executável
Decisão transversal da Tempo, 23/10/2026: percurso adaptativo com entradas por ideia/oportunidade/sector/localização; perguntar uma vez, preservar edições deliberadas e contexto entre ecrãs/autenticação; seleccionar opções únicas; ocultar exigências não aplicáveis sem confundir ausência de informação com dispensa; conservar especificidades na selecção múltipla. Mostrar posição, pendências e próxima acção. Relatórios/exportações seguem a mesma aplicabilidade. Origem: pedido expresso da Tempo nesta data; implementação incremental nos componentes existentes, sem novos aprovadores.
Incremento de apresentação: o dossier conserva as 19 etapas e o histórico, mas apresenta formulários apenas das etapas com preparação permitida. As restantes têm resumo do fundamento e podem regressar ao rever a decisão. A ferramenta Terra/DUAT deixa de ser proposta quando `land_needed=no`; `unknown` mantém a preparação disponível. Relatórios do plano reúnem requisitos documentais `not_applicable` numa explicação, mantendo requisitos em falta ou indeterminados. Esta alteração não remove obrigações jurídicas nem modifica dados guardados. A aplicação transversal a todos os formulários, entradas e exportações permanece por conferir; não se encerra esse requisito por documentação.
| Menu / ecrã | Componente React | Campos / entidade / tabela | Rota ou API | Perfil / regra | Evidência |
| ------------------------------ | ------------------------------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------ | --------------------------------------------------------- |
| Preparação adaptativa / relatório | `investor-path/show`, `investment-requirements`, `project-dossier-report` | Aplicabilidade e contexto existentes; histórico preservado | `/investimentos/{id}`, `/aplicabilidade`; exportações existentes do plano | Tempo, 23/10/2026: ocultar preparação sem aplicação; desconhecido não é dispensa | Incremento QA: `InvestmentCorridorContextTest::test_nonapplicable_land_hides_the_action_and_reopening_recovers_saved_evidence` (18 asserções); relatório 11 testes; types/build; cenário sintético no navegador. Cobertura transversal ainda parcial. |
| Dossier → simulador sem repetir dados | `dossier-prefill`, `simulation-form`, `context-inheritance` | Campos existentes, `context_reuse`, `selection_labels`; sem migration | `/investimentos/{id}/contexto`, `/simulador-fiscal?context={id}` | Resumos apenas para valores herdados intactos; alteração deliberada preservada; perfil/detalhe/regime não presumidos | Incremento QA de continuidade, 23/10/2026: 12 testes de reutilização e 14 asserções de nomes exactos; cobertura geral permanece parcial |
| Situação do terreno adaptada | `simulation-form`, `investment-land-preparation`, `project-dossier`; `SimulationFormDrafts` | `land_needed` existente; situação e rótulo conservados no rascunho cifrado | APIs existentes de contexto/rascunho e exportações do plano | Tempo: Não oculta, Sim recupera e permite editar, Por definir identifica pendência sem bloquear rascunho | Delta QA 23/10/2026: 4 testes frontend e 32 asserções API; sem migration; homologação não alterada |
| Início | `PortalInvestidor` | Sectores, oportunidades, recursos publicados | `/`, APIs existentes de homepage | Visitante; contagens do cadastro | `PublicPortalNavigationTest`, navegador |
| Catálogo / Educação | `investment/catalogue` | Filtros, educação, pivots, facetas | `/oportunidades`, `/educacao`; `/api/v1/investment/search` | Visitante; filtros em intersecção, montantes com moeda | `InvestmentUnificationTest` |
| Cluster / Corredor / Âncora | `portfolio/index`, `portfolio/show` | Dossiers, relações, cadeias e oportunidades | `/clusters`, `/corredores`, `/projectos-ancora`, `/carteira/{id}` | Visitante; dossier publicado; cadeia pública | `InvestmentPortfolioTest`, testes territoriais |
| Continuidade carteira → oportunidade → dossier → simulador | Componentes de carteira, `investment/detail`, contexto existente | Origem documental por oportunidade no payload cifrado; associações existentes, sem inferência | `from_dossier` e território no arranque; rascunho, prévia, confirmação e simulador existentes | Mesmo percurso integrado; origem relacionada/publicada; respostas deliberadas conservadas | Visibilidade: 29 IDs/títulos conferidos anteriormente. Delta QA: seis testes/151 asserções API, cinco testes de entrada territorial, um de relatório; instalação na homologação pendente |
| Ficha | `investment/detail` | Oportunidade, contexto, relações, fonte | `/oportunidades/{slug}`; `/api/v1/investment/opportunities/{slug}` | Visitante; rascunhos não acessíveis | `PublicPortalNavigationTest`, `InvestmentUnificationTest` |
| Mapa | Componente `Map` em catálogo | `geo_locations`, `geo_geometries`, `opportunity_locations` | `/api/v1/investment/map` | Visitante; dois níveis de classificação pública | Teste PostGIS e estados vazios |
| Comparar / exportar / sugerir | `investment/catalogue` | IDs publicados, dados de catálogo | `/compare`, `/export`, `/suggest` sob prefixo da API | Visitante; 4 comparações, 1.000 linhas/pontos; CSV protegido | Teste de publicação e fórmula CSV |
@@ -1020,53 +1300,53 @@ Reversão preparada em `REVERSAO-APRESENTACAO-INTEGRAL.json` e `reverse-authoriz
## Saldo dos 43 componentes
**12 disponíveis; 12 instalados mas desabilitados/por disponibilizar; 15 não demonstrados; 4 ausentes por serem incrementos QA excluídos.** “Disponível” tem o âmbito e limite indicado na linha; não certifica todos os critérios históricos do componente. Acesso aponta para o ecrã existente e não concede permissões: os acessos administrativos requerem as autorizações/MFA já existentes.
**Saldo histórico de 19/10/2026 — não representa o estado actual.** Conservado para rastreabilidade. A coluna final reconcilia as entregas posteriores e a leitura de 23/10, sem retirar aceitações por falta de nova prova. Disponibilidade, conclusão integral e ausência de dados são estados distintos.
| Nº | Componente | Estado | Acesso e limitação |
|---:|---|---|---|
| 1 | Documentos públicos | Instalado mas desabilitado | [Abrir](https://teste-investidor.tempo.co.mz/documentos) · 29 fichas privadas; zero publicação adicional. Consulta pública do acervo permanece fechada. |
| 2 | Ficha integral dos parques | Instalado mas desabilitado | [Abrir](https://teste-investidor.tempo.co.mz/zonas) · 16 fichas privadas/pendentes, 19 relações territoriais; publicação e gestão autorizada separadas. |
| 3 | Navegação contextual e manifestação de interesse | Disponível | [Abrir](https://teste-investidor.tempo.co.mz/investimentos/iniciar) · Início, contexto e navegação abrem; gravação/recuperação aceites em QA reutilizadas. Sem dossier real no destino. |
| 4 | Referências económicas — lote mínimo | Instalado mas desabilitado | [Abrir](https://teste-investidor.tempo.co.mz/management/investment/references) · 11 actividades presentes; cinco novas referências transportadas. Activação/publicação do lote não executada. |
| 5 | Sectores e duplicação visual | Disponível | [Abrir](https://teste-investidor.tempo.co.mz/sectores) · Ecrã aberto: dez sectores principais, especialização Fintech sem cartão principal duplicado; 12 registos conservados. |
| 6 | Duplicados distintos — deteção e consolidação | Não demonstrado | [Abrir](https://teste-investidor.tempo.co.mz/management/sector-reconciliation) · Código presente; consolidação de registos distintos não demonstrada no destino. |
| 7 | Fintech — enquadramento específico | Instalado mas desabilitado | [Abrir](https://teste-investidor.tempo.co.mz/sectores/mercado-financeiro/fintech) · Código instalado; artigo de origem permanece privado, sem publicação adicional. |
| 8 | Legado — conteúdo restante | Instalado mas desabilitado | [Abrir](https://teste-investidor.tempo.co.mz/sectores) · Adaptador habilitado; artigos/imagens transportados privadamente, sem os publicar. |
| 9 | Piloto agronegócio | Instalado mas desabilitado | [Abrir](https://teste-investidor.tempo.co.mz/sectores) · Originais privados; seis oportunidades sintéticas excluídas. Nenhuma oportunidade real publicada. |
| 10 | Percurso do investidor — 19 etapas | Disponível | [Abrir](https://teste-investidor.tempo.co.mz/investimentos) · Início e lista própria abrem; definição de 19 etapas instalada. Não repetidas as 19 etapas aceites. |
| 11 | Fiscal/contributivo e exportações | Não demonstrado | [Abrir](https://teste-investidor.tempo.co.mz/simulador-fiscal) · Formulário aberto; cenários fiscais/contributivos/exportação e parâmetros efectivos não certificados nesta execução. |
| 12 | Negócios existentes e classificação | Disponível | [Abrir](https://teste-investidor.tempo.co.mz/negocios) · Catálogo abre e módulo habilitado; zero negócios copiados. Gestão própria sujeita às permissões existentes. |
| 13 | Negócios similares | Não demonstrado | [Abrir](https://teste-investidor.tempo.co.mz/negocios) · Código instalado; comparação com negócios reais não demonstrada. |
| 14 | Importação de empresas/empreendimentos | Não demonstrado | [Abrir](https://teste-investidor.tempo.co.mz/management/negocios) · Mecanismo presente; nenhuma empresa/transacção local ou QA transferida. Operador de gestão por definir. |
| 15 | Fundos | Não demonstrado | [Abrir](https://teste-investidor.tempo.co.mz/simulador-fiscal) · Fontes instaladas; critérios específicos de fundos não demonstrados no destino. |
| 16 | Comparador societário e modelos | Não demonstrado | [Abrir](https://teste-investidor.tempo.co.mz/simulador-fiscal) · Fontes instaladas; comparação societária/modelos não demonstrados nesta execução. |
| 17 | Capitalização/private equity e Bolsa | Não demonstrado | [Abrir](https://teste-investidor.tempo.co.mz/simulador-fiscal) · Fontes instaladas; cenários específicos de capitalização/Bolsa não demonstrados no destino. |
| 18 | Checklists versionadas | Não demonstrado | [Abrir](https://teste-investidor.tempo.co.mz/balcao-virtual/servicos) · Definições presentes; checklist aplicada a dossier legítimo continua por demonstrar. |
| 19 | Indicadores de utilização | Não demonstrado | [Abrir](https://teste-investidor.tempo.co.mz/dashboard) · Código presente; indicadores com dados reais e permissões adequadas não demonstrados. |
| 20 | Jornadas educativas | Disponível | [Abrir](https://teste-investidor.tempo.co.mz/educacao) · Área abre e flag habilitada; nenhum percurso educativo completo foi repetido. |
| 21 | Partilha documental individual e variantes de sala | Não demonstrado | [Abrir](https://teste-investidor.tempo.co.mz/parcerias/painel) · Estruturas de salas/partilha instaladas; participantes, consentimentos e partilha real não demonstrados. |
| 22 | Restauro, modalidades e SLA | Não demonstrado | [Abrir](https://teste-investidor.tempo.co.mz/balcao-virtual/painel) · Backup/restauro coberto separadamente; modalidades/SLA não homologados. Escalonamento continua desligado como local. |
| 23 | Cartografia e consumidores territoriais restantes | Não demonstrado | [Abrir](https://teste-investidor.tempo.co.mz/oportunidades) · Catálogos/mapas abrem. 1 921 referências geográficas no alvo versus 1 990 locais; diferença não reconciliada por item fora do manifesto. |
| 24 | Acessibilidade e restantes consumidores | Não demonstrado | [Abrir](https://teste-investidor.tempo.co.mz/oportunidades) · Mensagens R2 preservadas; não se declara certificação integral de acessibilidade de todos os módulos. |
| 25 | Fechos de operação/avaliação documental | Não demonstrado | [Abrir](https://teste-investidor.tempo.co.mz/balcao-virtual/painel) · Sem processo operacional legítimo para demonstrar fecho/avaliação documental. |
| 26 | Conta simples, login e MFA privilegiado | Disponível | [Abrir](https://teste-investidor.tempo.co.mz/login) · Login e desafio MFA reais concluídos; conta simples R2 e controlos privilegiados preservados. |
| 27 | Manifestação simples, recibo e acompanhamento do interessado — R2 | Disponível | [Abrir](https://teste-investidor.tempo.co.mz/investidor/manifestacoes) · Recorte R2 conservado, com provas aceites reutilizadas; nenhuma manifestação fictícia criada. |
| 28 | Pesquisa — estados do recorte R2 | Disponível | [Abrir](https://teste-investidor.tempo.co.mz/oportunidades) · Catálogos abrem; quatro estados e protecção contra respostas antigas preservados do R2, sem repetir ensaios aceites. |
| 29 | Biblioteca privada própria, scanner e recuperação — DOC01 | Disponível | [Abrir](https://teste-investidor.tempo.co.mz/management/portal-services) · Conta de custódia vê 30 ficheiros limpos. Novo original recuperado no navegador com SHA-256 igual à fonte; sem gestão global. |
| 30 | Backup/restauro e reversão do recorte R2 + DOC01 | Disponível | Backup fresco consistente: 144 tabelas restauradas iguais, 34 ficheiros/configuração conferidos, recuperação portável e metadados Linux/PG protegidos. Reversão de apresentação preparada. |
| 31 | Balcão Virtual | Disponível | [Abrir](https://teste-investidor.tempo.co.mz/balcao-virtual/painel) · Painel e serviço existente abrem; dez tabelas base instaladas, definição interna project-preparation v5 transportada. |
| 32 | Vínculo positivo e recuperação do dossier | Não demonstrado | [Abrir](https://teste-investidor.tempo.co.mz/investimentos) · Vínculo documental positivo pendente: zero dossiers da conta no destino. Não foram criados dados fictícios. |
| 33 | Cadastro canónico e recursos de oportunidade | Instalado mas desabilitado | [Abrir](https://teste-investidor.tempo.co.mz/management/opportunities) · Editor/schema instalados; zero oportunidades. Gestão/revisão exigem atribuições existentes ainda não configuradas. |
| 34 | Perfis, revisão independente e publicação | Instalado mas desabilitado | [Abrir](https://teste-investidor.tempo.co.mz/perfis/gestao) · Schema e fluxos presentes; zero atribuições/titulares. Nenhum papel ou privilégio concedido automaticamente. |
| 35 | Bolsa de parcerias e salas internas | Disponível | [Abrir](https://teste-investidor.tempo.co.mz/parcerias) · Catálogo e mapa abrem, nove tabelas de relações/salas instaladas; colaboração com participantes reais não demonstrada. |
| 36 | Onde se localiza / associações territoriais e produtivas | Ausente | Harmonização territorial apenas QA, expressamente excluída da revisão autorizada. |
| 37 | Lote privado Inhambane —30 fichas | Ausente | 30 fichas Inhambane apenas QA, não importadas nem publicadas no destino. |
| 38 | Enquadramento das12 fichas de programas/carteira | Ausente | Enquadramento das 12 fichas permanece no incremento QA separado. |
| 39 | Originais partilhados e referência documental no dossier — Inhambane | Ausente | Reutilização documental Inhambane/dossier permanece no incremento QA separado. |
| 40 | Orientação assistida/IA | Instalado mas desabilitado | [Abrir](https://teste-investidor.tempo.co.mz/management/ai) · Código presente; integração desligada. Credencial própria do destino/chamada externa não confirmadas. |
| 41 | Integração externa SuiteCRM | Instalado mas desabilitado | Permanece desligada como na versão local; nenhuma credencial ou envio externo copiado/executado. |
| 42 | Operação administrativa e resposta da equipa na homologação | Instalado mas desabilitado | [Abrir](https://teste-investidor.tempo.co.mz/management/interests) · Nenhuma conta com gestão global/atribuições operacionais. Custódia documental não autoriza resposta/revisão da equipa. |
| 43 | Gestão editorial da biblioteca | Instalado mas desabilitado | [Abrir](https://teste-investidor.tempo.co.mz/management/portal-services) · Schema editorial instalado; publicação/edição pública não concedida à conta de custódia. |
| Nº | Componente | Estado histórico | Acesso e limitação históricos | Saldo de preservação em 23/10/2026 |
|---:|---|---|---|---|
| 1 | Documentos públicos | Instalado mas desabilitado | [Abrir](https://teste-investidor.tempo.co.mz/documentos) · 29 fichas privadas; zero publicação adicional. Consulta pública do acervo permanece fechada. | Disponível: 29 recursos públicos; recibo editorial 22/10. Os 20 artigos conservam a aceitação anterior. |
| 2 | Ficha integral dos parques | Instalado mas desabilitado | [Abrir](https://teste-investidor.tempo.co.mz/zonas) · 16 fichas privadas/pendentes, 19 relações territoriais; publicação e gestão autorizada separadas. | Disponibilizado localmente em 18/10; destino conserva 16 fichas, 8 publicadas. Recorte integral não é reaprovado por este inventário. |
| 3 | Navegação contextual e manifestação de interesse | Disponível | [Abrir](https://teste-investidor.tempo.co.mz/investimentos/iniciar) · Início, contexto e navegação abrem; gravação/recuperação aceites em QA reutilizadas. Sem dossier real no destino. | Navegação/percurso preservados; carteira reposta em 23/10: 29 dossiers, incluindo 15 projectos âncora. |
| 4 | Referências económicas — lote mínimo | Instalado mas desabilitado | [Abrir](https://teste-investidor.tempo.co.mz/management/investment/references) · 11 actividades presentes; cinco novas referências transportadas. Activação/publicação do lote não executada. | Código e referências presentes; manter o âmbito da autorização anterior, sem inferir publicação de todas as opções. |
| 5 | Sectores e duplicação visual | Disponível | [Abrir](https://teste-investidor.tempo.co.mz/sectores) · Ecrã aberto: dez sectores principais, especialização Fintech sem cartão principal duplicado; 12 registos conservados. | Entrega de 18/10 conservada no código/histórico; sem remoção constatada nesta leitura. |
| 6 | Duplicados distintos — deteção e consolidação | Não demonstrado | [Abrir](https://teste-investidor.tempo.co.mz/management/sector-reconciliation) · Código presente; consolidação de registos distintos não demonstrada no destino. | Sem desaparecimento demonstrado; conserva o âmbito parcial/não demonstrado anterior, sem promover conclusão nem reabrir provas aceites. |
| 7 | Fintech — enquadramento específico | Instalado mas desabilitado | [Abrir](https://teste-investidor.tempo.co.mz/sectores/mercado-financeiro/fintech) · Código instalado; artigo de origem permanece privado, sem publicação adicional. | Fintech público no lote aceite; especialização preservada. Não reabrir a organização sectorial aprovada. |
| 8 | Legado — conteúdo restante | Instalado mas desabilitado | [Abrir](https://teste-investidor.tempo.co.mz/sectores) · Adaptador habilitado; artigos/imagens transportados privadamente, sem os publicar. | 20 artigos públicos e 28 privados cadastrados no destino; dois originais incompletos. Recibo 37a8eb01. |
| 9 | Piloto agronegócio | Instalado mas desabilitado | [Abrir](https://teste-investidor.tempo.co.mz/sectores) · Originais privados; seis oportunidades sintéticas excluídas. Nenhuma oportunidade real publicada. | Piloto Agronegócio concluído: artigo e imagem preservados; não é uma oportunidade empresarial concreta. |
| 10 | Percurso do investidor — 19 etapas | Disponível | [Abrir](https://teste-investidor.tempo.co.mz/investimentos) · Início e lista própria abrem; definição de 19 etapas instalada. Não repetidas as 19 etapas aceites. | Percurso e preparação privada habilitados no destino; entrega adaptativa de 22/10. Zero dossiers pessoais não é perda de função. |
| 11 | Fiscal/contributivo e exportações | Não demonstrado | [Abrir](https://teste-investidor.tempo.co.mz/simulador-fiscal) · Formulário aberto; cenários fiscais/contributivos/exportação e parâmetros efectivos não certificados nesta execução. | Tesouraria/exportações e consumidor IVA têm entregas delimitadas em 21–22/10; restantes variantes não promovidas a concluídas. |
| 12 | Negócios existentes e classificação | Disponível | [Abrir](https://teste-investidor.tempo.co.mz/negocios) · Catálogo abre e módulo habilitado; zero negócios copiados. Gestão própria sujeita às permissões existentes. | Módulo habilitado e fluxos conservados. Ausência de anúncios não demonstra desaparecimento de código. |
| 13 | Negócios similares | Não demonstrado | [Abrir](https://teste-investidor.tempo.co.mz/negocios) · Código instalado; comparação com negócios reais não demonstrada. | Sem desaparecimento demonstrado; conserva o âmbito parcial/não demonstrado anterior, sem promover conclusão nem reabrir provas aceites. |
| 14 | Importação de empresas/empreendimentos | Não demonstrado | [Abrir](https://teste-investidor.tempo.co.mz/management/negocios) · Mecanismo presente; nenhuma empresa/transacção local ou QA transferida. Operador de gestão por definir. | Sem desaparecimento demonstrado; conserva o âmbito parcial/não demonstrado anterior, sem promover conclusão nem reabrir provas aceites. |
| 15 | Fundos | Não demonstrado | [Abrir](https://teste-investidor.tempo.co.mz/simulador-fiscal) · Fontes instaladas; critérios específicos de fundos não demonstrados no destino. | Sem desaparecimento demonstrado; conserva o âmbito parcial/não demonstrado anterior, sem promover conclusão nem reabrir provas aceites. |
| 16 | Comparador societário e modelos | Não demonstrado | [Abrir](https://teste-investidor.tempo.co.mz/simulador-fiscal) · Fontes instaladas; comparação societária/modelos não demonstrados nesta execução. | Sem desaparecimento demonstrado; conserva o âmbito parcial/não demonstrado anterior, sem promover conclusão nem reabrir provas aceites. |
| 17 | Capitalização/private equity e Bolsa | Não demonstrado | [Abrir](https://teste-investidor.tempo.co.mz/simulador-fiscal) · Fontes instaladas; cenários específicos de capitalização/Bolsa não demonstrados no destino. | Sem desaparecimento demonstrado; conserva o âmbito parcial/não demonstrado anterior, sem promover conclusão nem reabrir provas aceites. |
| 18 | Checklists versionadas | Não demonstrado | [Abrir](https://teste-investidor.tempo.co.mz/balcao-virtual/servicos) · Definições presentes; checklist aplicada a dossier legítimo continua por demonstrar. | Sem desaparecimento demonstrado; conserva o âmbito parcial/não demonstrado anterior, sem promover conclusão nem reabrir provas aceites. |
| 19 | Indicadores de utilização | Não demonstrado | [Abrir](https://teste-investidor.tempo.co.mz/dashboard) · Código presente; indicadores com dados reais e permissões adequadas não demonstrados. | Sem desaparecimento demonstrado; conserva o âmbito parcial/não demonstrado anterior, sem promover conclusão nem reabrir provas aceites. |
| 20 | Jornadas educativas | Disponível | [Abrir](https://teste-investidor.tempo.co.mz/educacao) · Área abre e flag habilitada; nenhum percurso educativo completo foi repetido. | Jornadas habilitadas; cartões educativos disponibilizados. Mantido o âmbito parcial da educação. |
| 21 | Partilha documental individual e variantes de sala | Não demonstrado | [Abrir](https://teste-investidor.tempo.co.mz/parcerias/painel) · Estruturas de salas/partilha instaladas; participantes, consentimentos e partilha real não demonstrados. | Sem desaparecimento demonstrado; conserva o âmbito parcial/não demonstrado anterior, sem promover conclusão nem reabrir provas aceites. |
| 22 | Restauro, modalidades e SLA | Não demonstrado | [Abrir](https://teste-investidor.tempo.co.mz/balcao-virtual/painel) · Backup/restauro coberto separadamente; modalidades/SLA não homologados. Escalonamento continua desligado como local. | Sem desaparecimento demonstrado; conserva o âmbito parcial/não demonstrado anterior, sem promover conclusão nem reabrir provas aceites. |
| 23 | Cartografia e consumidores territoriais restantes | Não demonstrado | [Abrir](https://teste-investidor.tempo.co.mz/oportunidades) · Catálogos/mapas abrem. 1 921 referências geográficas no alvo versus 1 990 locais; diferença não reconciliada por item fora do manifesto. | Demarcação reposta: 408 geometrias. Polígonos de localidades e correspondências já pendentes não são nova regressão. |
| 24 | Acessibilidade e restantes consumidores | Não demonstrado | [Abrir](https://teste-investidor.tempo.co.mz/oportunidades) · Mensagens R2 preservadas; não se declara certificação integral de acessibilidade de todos os módulos. | Sem desaparecimento demonstrado; conserva o âmbito parcial/não demonstrado anterior, sem promover conclusão nem reabrir provas aceites. |
| 25 | Fechos de operação/avaliação documental | Não demonstrado | [Abrir](https://teste-investidor.tempo.co.mz/balcao-virtual/painel) · Sem processo operacional legítimo para demonstrar fecho/avaliação documental. | Sem desaparecimento demonstrado; conserva o âmbito parcial/não demonstrado anterior, sem promover conclusão nem reabrir provas aceites. |
| 26 | Conta simples, login e MFA privilegiado | Disponível | [Abrir](https://teste-investidor.tempo.co.mz/login) · Login e desafio MFA reais concluídos; conta simples R2 e controlos privilegiados preservados. | Login/MFA e designações reais documentados; sem alteração de papéis nesta passagem. |
| 27 | Manifestação simples, recibo e acompanhamento do interessado — R2 | Disponível | [Abrir](https://teste-investidor.tempo.co.mz/investidor/manifestacoes) · Recorte R2 conservado, com provas aceites reutilizadas; nenhuma manifestação fictícia criada. | R2 e percursos posteriores conservados; reutilizar as provas aceites. |
| 28 | Pesquisa — estados do recorte R2 | Disponível | [Abrir](https://teste-investidor.tempo.co.mz/oportunidades) · Catálogos abrem; quatro estados e protecção contra respostas antigas preservados do R2, sem repetir ensaios aceites. | Pesquisa e selectores corrigidos em 22/10; a carteira vazia é tratada como perda separada. |
| 29 | Biblioteca privada própria, scanner e recuperação — DOC01 | Disponível | [Abrir](https://teste-investidor.tempo.co.mz/management/portal-services) · Conta de custódia vê 30 ficheiros limpos. Novo original recuperado no navegador com SHA-256 igual à fonte; sem gestão global. | Biblioteca, scan e recuperação disponíveis: 59 recursos, 29 públicos e 30 privados. |
| 30 | Backup/restauro e reversão do recorte R2 + DOC01 | Disponível | Backup fresco consistente: 144 tabelas restauradas iguais, 34 ficheiros/configuração conferidos, recuperação portável e metadados Linux/PG protegidos. Reversão de apresentação preparada. | Backup/restauro 22/10 documentado: 174 tabelas. Reposição activa terá backup próprio antes da escrita. |
| 31 | Balcão Virtual | Disponível | [Abrir](https://teste-investidor.tempo.co.mz/balcao-virtual/painel) · Painel e serviço existente abrem; dez tabelas base instaladas, definição interna project-preparation v5 transportada. | Balcão e definições instalados; zero casos reais no destino não é perda de funcionalidade. |
| 32 | Vínculo positivo e recuperação do dossier | Não demonstrado | [Abrir](https://teste-investidor.tempo.co.mz/investimentos) · Vínculo documental positivo pendente: zero dossiers da conta no destino. Não foram criados dados fictícios. | Recuperação de contexto demonstrada em QA nas entregas posteriores; ausência de caso real não reabre a prova aceite. |
| 33 | Cadastro canónico e recursos de oportunidade | Instalado mas desabilitado | [Abrir](https://teste-investidor.tempo.co.mz/management/opportunities) · Editor/schema instalados; zero oportunidades. Gestão/revisão exigem atribuições existentes ainda não configuradas. | Cadastro e recursos disponibilizados; 30 oportunidades com resumos em preparação publicados. |
| 34 | Perfis, revisão independente e publicação | Instalado mas desabilitado | [Abrir](https://teste-investidor.tempo.co.mz/perfis/gestao) · Schema e fluxos presentes; zero atribuições/titulares. Nenhum papel ou privilégio concedido automaticamente. | Perfis e atribuições existentes preservados; inventário não cria decisão organizacional nova. |
| 35 | Bolsa de parcerias e salas internas | Disponível | [Abrir](https://teste-investidor.tempo.co.mz/parcerias) · Catálogo e mapa abrem, nove tabelas de relações/salas instaladas; colaboração com participantes reais não demonstrada. | Bolsa e percursos existentes preservados; variantes com participantes reais mantêm saldo próprio. |
| 36 | Onde se localiza / associações territoriais e produtivas | Ausente | Harmonização territorial apenas QA, expressamente excluída da revisão autorizada. | Estado Ausente superado: integração territorial entregue em 20–22/10; catálogos e 408 limites conservados. |
| 37 | Lote privado Inhambane —30 fichas | Ausente | 30 fichas Inhambane apenas QA, não importadas nem publicadas no destino. | Estado Ausente superado: 30 fichas Inhambane transportadas e resumos divulgados em 22/10. |
| 38 | Enquadramento das12 fichas de programas/carteira | Ausente | Enquadramento das 12 fichas permanece no incremento QA separado. | Estado Ausente superado: enquadramento de 11 programas e uma carteira incluído na entrega posterior. |
| 39 | Originais partilhados e referência documental no dossier — Inhambane | Ausente | Reutilização documental Inhambane/dossier permanece no incremento QA separado. | Estado Ausente superado: originais privados/referências e contexto incluídos na entrega posterior; não reimportar. |
| 40 | Orientação assistida/IA | Instalado mas desabilitado | [Abrir](https://teste-investidor.tempo.co.mz/management/ai) · Código presente; integração desligada. Credencial própria do destino/chamada externa não confirmadas. | Sem regressão demonstrada: integração externa não fechada; não inventar credencial nem activar fornecedor. |
| 41 | Integração externa SuiteCRM | Instalado mas desabilitado | Permanece desligada como na versão local; nenhuma credencial ou envio externo copiado/executado. | Desabilitada também no local por opção anterior; não desapareceu por efeito da entrega editorial. |
| 42 | Operação administrativa e resposta da equipa na homologação | Instalado mas desabilitado | [Abrir](https://teste-investidor.tempo.co.mz/management/interests) · Nenhuma conta com gestão global/atribuições operacionais. Custódia documental não autoriza resposta/revisão da equipa. | Estado antigo superado: conta real e gestão autorizada demonstradas nas entregas posteriores. Sem novos papéis. |
| 43 | Gestão editorial da biblioteca | Instalado mas desabilitado | [Abrir](https://teste-investidor.tempo.co.mz/management/portal-services) · Schema editorial instalado; publicação/edição pública não concedida à conta de custódia. | Gestão autónoma entregue; edição, prévia, gravação e recuperação dos 28 privados demonstradas no destino. |
## Operação corrente e pendências
@@ -2481,3 +2761,19 @@ O leitor mantém a estrutura HTML sanitizada do original enquanto o corpo não f
5. A equipa decide e executa a publicação individual no portal, após rever/corrigir/pré-visualizar. Transporte privado, autorização dos metadados e publicação são etapas distintas. Não publicar artigos como efeito da instalação ou da conferência do transporte. Verificar só o delta do destino, sem repetir os 20 aceites ou as provas QA.
**Reversão e dependências remanescentes.** Para retirar conteúdo, usar a retirada pela interface e preservar originais, revisões e auditoria. Se for necessário reverter código, manter dados e ficheiros; não restaurar um backup antigo por cima de edições/publicações posteriores. Preservar novo snapshot e reconciliar o delta antes de qualquer restauro. A revisão/integração Git, transporte privado, backup/restauro e upload/scan autenticado iniciais ainda são trabalho de entrega; não estão declarados concluídos nem constituem tarefas recorrentes de cada edição. Depois de disponibilizado este delta, rever, corrigir, pré-visualizar, guardar, recuperar, publicar e retirar estes conteúdos não exigem comandos ou Codex. A revisão editorial efectiva dos textos pela equipa continua distinta da demonstração do percurso. Geometrias não foram alteradas.
### Legado — cadastro privado concluído na homologação, 22/10/2026
**Actualização da entrega anterior:** a candidata `fb4e7d89` foi revista tecnicamente e integrada pelo Git em `65eddf6bc7025c81cbd7b0c5918b6ae8bdb1c1f6`, revisão funcional servida durante esta conferência. Sem migration ou novas dependências. Backup anterior às escritas: dump SHA-256 `8658b5fa0d1ef16c9fc9be90969badde6007443aa474fe4a9377f16d80fe6e92`, arquivo de ficheiros/configuração `1d98b6200ffae11bb07dc600daa6416b3b1b90735de42791858a07a0caa63552`; restauro isolado com 174 tabelas e 341 ficheiros conferidos. Backup e pacote continuam privados em `/var/backups/investmoz-legacy-editorial-20261022/`. O transporte já concluído não foi repetido nesta retoma.
**28 originais cadastrados, 28 recursos privados, zero publicações.** Reutilizado no servidor o pacote v2 com SHA-256 `177c50d1cc3b1610e62123a2f8b532711a15f875aac2ee0e372636c2f747e8a2`. A ingestão usou os endpoints HTTPS existentes de upload, cadastro e edição, com referência à sessão real recente de Osmane Nala (actor 15), obtida após login e MFA normais; sem criar sessão/MFA, atribuir papéis ou escrever directamente nas tabelas de conteúdo. O operador autenticado executou esta passagem inicial a partir dos ficheiros já transportados; a equipa não precisa deste executor para rever ou publicar os recursos agora disponíveis. O limite de uploads foi respeitado. Uma resposta intermédia não pôde ser descodificada pelo invólucro técnico; a retoma encontrou os originais por hash e continuou sem nova cópia dos já cadastrados. Não foi atribuída uma causa não demonstrada a essa interrupção.
Cada original foi resolvido por hash, com conferência de unicidade antes do cadastro; a classificação permaneceu `internal`, os direitos/estado documental anteriores não foram convertidos em autorização de publicação e os scans reais ficaram limpos, com hash analisado igual aos bytes actuais. IDs do destino 32–59, distintos dos IDs QA. A identidade de versão inclui o identificador do original, evitando colisões entre artigos da mesma data. Os snapshots QA permanecem como proveniência no pacote privado; o histórico activo foi criado pelos endpoints com o actor real, sem importar identidades QA como autores da auditoria. A edição QA do artigo 62413, incluindo a nota explicitamente marcada QA, foi conservada para revisão da equipa.
**Conferência operacional do delta, concluída às 23h54 de 22/10/2026 (África/Johannesburgo; 21h54 UTC):** 28/28 encontrados pela pesquisa de rascunhos; 28/28 editores e leituras privadas responderam 200, com identidade, fonte e corpo recuperado correspondentes; 28/28 endereços públicos `/artigos/{id}` devolveram 404 sem sessão. Na interface, a conta real pesquisou «15/30», abriu o artigo 62413, preparou a prévia, guardou o mesmo corpo como revisão 3 e recuperou essa revisão, sempre privada. A gravação de conferência não acrescentou texto de demonstração ao corpo. Não se repetiram as provas QA ou as consultas HTTP dos 20 artigos públicos.
Preservação por comparação dos registos preexistentes: **32 documentos, 31 recursos, 63 revisões e 19 associações**, incluindo os 20 artigos públicos, permaneceram iguais. Também preservados 17 utilizadores, atribuições de papéis, par de superutilizadores, aprovações de segurança, 1.921 entradas territoriais e 408 geometrias. Nenhuma publicação foi feita por esta entrega. As 16 referências de imagens no corpo permanecem discriminadas; a capa comum é o exemplar anterior, explicitamente referenciado nos originais e reutilizado pelo hash exacto `28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa`, não uma imagem presumida. Referências sem exemplar verificado não foram preenchidas com substitutos.
**Guia da equipa no destino:** abrir a [gestão única](https://teste-investidor.tempo.co.mz/gestao-conteudos) → **Gerir conteúdos, versões e histórico** → escolher **Rascunhos**, pesquisar o título e abrir **Editar artigo** ou **Ler / pré-visualizar artigo**. Corrigir o texto e o tratamento de imagem → **Pré-visualizar alterações** → **Guardar artigo como rascunho** → **Ler revisão guardada**. Usar **Voltar ao acervo para metadados e publicação** para corrigir título, descrição, origem, enquadramento, acesso e direitos. **Publicar** continua separado, conforme a autorização e os campos existentes, quando a equipa decidir publicar. Guardar uma edição de um artigo publicado retira-o até nova publicação expressa. Não há comandos ou intervenção do Codex neste percurso corrente.
Recibo: [cadastro, acessos e preservação no destino](legacy-editorial-installation-receipt-20261022.json). A [lista única dos 50](../v2/auditoria-portal/legado-correspondencia-integral.md) e o CSV existente contêm os endereços **privados** efectivos dos 28; não são links de artigos já publicados. Capturas locais em `outputs/legacy-editorial-install-20261022/`, incluindo `editor-recovered-homologation.png`. Mantêm-se dois originais incompletos e a revisão editorial dos textos pela equipa. A Tempo não declarou ter conferido commit, pacote ou guia. **A carteira vazia e os projectos âncora continuam como falha separada, sem causa confirmada; esta entrega não a resolve.** (Saldo histórico desta entrega de 22/10; superado pela reposição auditada e reconciliação de 23/10 descritas acima. Não representa o estado actual.)
@@ -1,26 +1,26 @@
ID legado,Título,Autor,Categoria,Publicado,Modificado,SHA256 conteúdo,URLs observadas,Recurso QA,Situação,Situação anterior à reconciliação 15/10/2026,Recurso ativo,Grupo restante,Imagem atual,Dependência concreta,Próxima ação,Evidência atual,Endereço no novo portal,Estado actual consolidado,Pendência actual,Conferência HTTP UTC,Grupo de preparação QA
171985,Agronegocio,tempo,Oportunidades,2026-04-06T09:19:07+00:00,2026-04-20T12:41:43+00:00,28383a11d95f13894381357d8f1227b33031d1d3ff74507da4ea8e5ce2470913,https://investemoz.tempo.co.mz/agronegocio,6,publicado,recurso único QA; não é oportunidade,13,publicados,"{""scope"": ""homologação"", ""href"": ""/documentos/12/view"", ""status"": ""referência na ficha; binário não reensaiado""}",Sem pendência de integração desta identidade; mantém-se a revisão editorial corrente.,Preservar a publicação; gerir edições e versões pela biblioteca existente.,outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado,https://teste-investidor.tempo.co.mz/agronegocio,Publicado; identidade e conteúdo consultáveis na homologação,Não foi feita aceitação visual ou conferência pela Tempo nesta passagem.,2026-10-22T21:05:03.369388+00:00,
62413,15/30 costa do sol com dua na zona de mupulene muito lindo,tempo,Categoria,2017-06-26T11:44:21+00:00,2017-06-26T11:44:21+00:00,c04ae821317ef556c01c3141ce75eeeeb5da7787e0c6849b1b3709b757edb9eb,https://investemoz.tempo.co.mz/1530-costa-do-sol-com-dua-na-zona-de-mupulene-muito-lindo,43,existente por integrar,inventariado; cadastro/versão por reconciliar,,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Rever/corrigir a nota de demonstração QA antes de publicar.,Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites.,legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados,,"Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Rever/corrigir a nota de demonstração QA antes de publicar.,,historico_noticioso
173220,Alumínio “Verde”,tempo,Oportunidades,2026-06-16T12:41:02+00:00,2026-06-16T12:41:06+00:00,c61f05a56bb5a5f13d9d5d54dbefd270cf9ac40320917d77aaad76cac56c7b97,https://investemoz.tempo.co.mz/aluminio-verde,51,existente por integrar,inventariado; cadastro/versão por reconciliar,,setorial,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites.,legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados,,"Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,,setorial
73945,Banco Mundial classifica de proibitivos juros praticados em Moçambique,tempo,Nas Redes,2017-07-31T19:55:24+00:00,2017-07-31T19:55:24+00:00,36ba82b8885b00a4ea2fd9e4b1d8cf304a8e78e591972682f0aa53717f648d19,https://investemoz.tempo.co.mz/banco-mundial-classifica-de-proibitivos-juros-praticados-em-mocambique,44,existente por integrar,inventariado; cadastro/versão por reconciliar,,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites.,legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados,,"Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,,historico_noticioso
62413,15/30 costa do sol com dua na zona de mupulene muito lindo,tempo,Categoria,2017-06-26T11:44:21+00:00,2017-06-26T11:44:21+00:00,c04ae821317ef556c01c3141ce75eeeeb5da7787e0c6849b1b3709b757edb9eb,https://investemoz.tempo.co.mz/1530-costa-do-sol-com-dua-na-zona-de-mupulene-muito-lindo,43,existente por integrar,inventariado; cadastro/versão por reconciliar,32,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. Rever/corrigir a nota de demonstração QA antes de publicar.,"Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos.",docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas,https://teste-investidor.tempo.co.mz/management/portal-services/resources/32/article/preview,"Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. Rever/corrigir a nota de demonstração QA antes de publicar.,,historico_noticioso
173220,Alumínio “Verde”,tempo,Oportunidades,2026-06-16T12:41:02+00:00,2026-06-16T12:41:06+00:00,c61f05a56bb5a5f13d9d5d54dbefd270cf9ac40320917d77aaad76cac56c7b97,https://investemoz.tempo.co.mz/aluminio-verde,51,existente por integrar,inventariado; cadastro/versão por reconciliar,50,setorial,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,"Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos.",docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas,https://teste-investidor.tempo.co.mz/management/portal-services/resources/50/article/preview,"Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,,setorial
73945,Banco Mundial classifica de proibitivos juros praticados em Moçambique,tempo,Nas Redes,2017-07-31T19:55:24+00:00,2017-07-31T19:55:24+00:00,36ba82b8885b00a4ea2fd9e4b1d8cf304a8e78e591972682f0aa53717f648d19,https://investemoz.tempo.co.mz/banco-mundial-classifica-de-proibitivos-juros-praticados-em-mocambique,44,existente por integrar,inventariado; cadastro/versão por reconciliar,33,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,"Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos.",docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas,https://teste-investidor.tempo.co.mz/management/portal-services/resources/33/article/preview,"Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,,historico_noticioso
173213,Bens de Consumo e Franquias,tempo,Oportunidades,2026-06-16T11:31:06+00:00,2026-06-16T11:32:10+00:00,abd5f36a0c2b89d2989334137aae5d59b3158da38d556013ccd55deaa5069e92,https://investemoz.tempo.co.mz/bens-de-consumo-franquias,29,publicado,inventariado; cadastro/versão por reconciliar,29,publicados,"{""scope"": ""homologação"", ""href"": ""/documentos/27/view"", ""status"": ""referência na ficha; binário não reensaiado""}",Sem pendência de integração desta identidade; mantém-se a revisão editorial corrente.,Preservar a publicação; gerir edições e versões pela biblioteca existente.,outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado,https://teste-investidor.tempo.co.mz/bens-de-consumo-franquias,Publicado; identidade e conteúdo consultáveis na homologação,Não foi feita aceitação visual ou conferência pela Tempo nesta passagem.,2026-10-22T21:05:03.369388+00:00,
172223,"Cabo Delgado - Pemba, A capital do turismo costeiro do norte de Moçambique",tempo,Turismo,2026-04-14T10:21:20+00:00,2026-04-14T10:41:05+00:00,c96b2430c44b8edc9cc1d0a7974c23290dd0207cff366fea8326bb28bcab8285,https://investemoz.tempo.co.mz/cabo-delgado-pemba-a-capital-do-turismo-costeiro-do-norte-de-mocambique/ | https://investemoz.tempo.co.mz/cabo-delgado-pemba-a-capital-do-turismo-costeiro-do-norte-de-mocambique,20,publicado,RecursoQA20; original/hash/ClamD/replay demonstrados. DivulgaçãoQAseparada;consumidorHTTP/desktop observado;não é oportunidade nem geometria.,25,publicados,"{""scope"": ""homologação"", ""href"": ""/documentos/27/view"", ""status"": ""referência na ficha; binário não reensaiado""}",Sem pendência de integração desta identidade; mantém-se a revisão editorial corrente.,Preservar a publicação; gerir edições e versões pela biblioteca existente.,outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado,https://teste-investidor.tempo.co.mz/cabo-delgado-pemba-a-capital-do-turismo-costeiro-do-norte-de-mocambique,Publicado; identidade e conteúdo consultáveis na homologação,Não foi feita aceitação visual ou conferência pela Tempo nesta passagem.,2026-10-22T21:05:03.369388+00:00,
173482,Checklist para Contratação e Direitos do Trabalho Doméstico,tempo,Trabalho Doméstico,2026-09-15T12:36:11+00:00,2026-09-15T12:36:14+00:00,309b51ebc53bf840f9014da733bc09f8d162138be4e6e9d5a72670e26cc8e482,https://investemoz.tempo.co.mz/checklist-para-contratacao-e-direitos-do-trabalho-domestico,52,existente por integrar,inventariado; cadastro/versão por reconciliar,,trabalho_domestico,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites.,legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados,,"Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,,trabalho_domestico
76474,"Coloque o seu número para o grupo de whatsapp, venda de acessórios de computador",tempo,Categoria,2017-08-08T17:29:51+00:00,2017-08-08T17:29:51+00:00,c8beeb94f936b3bfc69c720770bd84376e9f19b668d96185aaefbada05727010,https://investemoz.tempo.co.mz/coloque-o-seu-numero-para-o-grupo-de-whatsapp-venda-de-acessorios-de-computador,45,existente por integrar,inventariado; cadastro/versão por reconciliar,,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites.,legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados,,"Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,,historico_noticioso
173484,Direitos Especiais da Trabalhadora Doméstica no Regulamento,tempo,Trabalho Doméstico,2026-09-15T12:38:08+00:00,2026-09-15T12:38:11+00:00,34c31bb02a3ad0886f12ac2853283de2ffb89a925be3cda73b7ce9385c6caec4,https://investemoz.tempo.co.mz/direitos-especiais-da-trabalhadora-domestica-no-regulamento,64,existente por integrar,inventariado; cadastro/versão por reconciliar,,trabalho_domestico,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites.,legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados,,"Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,,trabalho_domestico
173482,Checklist para Contratação e Direitos do Trabalho Doméstico,tempo,Trabalho Doméstico,2026-09-15T12:36:11+00:00,2026-09-15T12:36:14+00:00,309b51ebc53bf840f9014da733bc09f8d162138be4e6e9d5a72670e26cc8e482,https://investemoz.tempo.co.mz/checklist-para-contratacao-e-direitos-do-trabalho-domestico,52,existente por integrar,inventariado; cadastro/versão por reconciliar,52,trabalho_domestico,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,"Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos.",docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas,https://teste-investidor.tempo.co.mz/management/portal-services/resources/52/article/preview,"Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,,trabalho_domestico
76474,"Coloque o seu número para o grupo de whatsapp, venda de acessórios de computador",tempo,Categoria,2017-08-08T17:29:51+00:00,2017-08-08T17:29:51+00:00,c8beeb94f936b3bfc69c720770bd84376e9f19b668d96185aaefbada05727010,https://investemoz.tempo.co.mz/coloque-o-seu-numero-para-o-grupo-de-whatsapp-venda-de-acessorios-de-computador,45,existente por integrar,inventariado; cadastro/versão por reconciliar,34,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,"Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos.",docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas,https://teste-investidor.tempo.co.mz/management/portal-services/resources/34/article/preview,"Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,,historico_noticioso
173484,Direitos Especiais da Trabalhadora Doméstica no Regulamento,tempo,Trabalho Doméstico,2026-09-15T12:38:08+00:00,2026-09-15T12:38:11+00:00,34c31bb02a3ad0886f12ac2853283de2ffb89a925be3cda73b7ce9385c6caec4,https://investemoz.tempo.co.mz/direitos-especiais-da-trabalhadora-domestica-no-regulamento,64,existente por integrar,inventariado; cadastro/versão por reconciliar,53,trabalho_domestico,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,"Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos.",docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas,https://teste-investidor.tempo.co.mz/management/portal-services/resources/53/article/preview,"Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,,trabalho_domestico
171981,Energia & Mineração,tempo,Oportunidades,2026-04-06T09:17:46+00:00,2026-04-30T13:35:32+00:00,e9a95f0c8176eea48a75bd2c5bce69f15308806988fa5203a688af7c40305e9c,https://investemoz.tempo.co.mz/energia-mineracao,7,publicado,RecursoQA7; original/hash/ClamD/replay demonstrados. DivulgaçãoQAseparada;consumidorHTTP/desktop observado;não é oportunidade nem geometria.,18,publicados,"{""scope"": ""homologação"", ""href"": ""/documentos/14/view"", ""status"": ""referência na ficha; binário não reensaiado""}",Sem pendência de integração desta identidade; mantém-se a revisão editorial corrente.,Preservar a publicação; gerir edições e versões pela biblioteca existente.,outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado,https://teste-investidor.tempo.co.mz/energia-mineracao,Publicado; identidade e conteúdo consultáveis na homologação,Não foi feita aceitação visual ou conferência pela Tempo nesta passagem.,2026-10-22T21:05:03.369388+00:00,
173222,Energia e Recursos Naturais,tempo,Oportunidades,2026-06-16T12:47:16+00:00,2026-06-16T12:51:30+00:00,6f8236b0441d61fce5171994ba6b82ffc8b5f2de3e8242de7be29248ed046ad2,https://investemoz.tempo.co.mz/energia-recursos-naturais,63,existente por integrar,inventariado; cadastro/versão por reconciliar,,setorial,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites.,legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados,,"Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,,setorial
62414,Frelimo e Zanu-PF: Evocados laços de irmandade,tempo,Categoria,2017-06-26T12:03:20+00:00,2017-06-26T12:03:20+00:00,013307965b8fcb349a0d936d844ed12437bda4c90389f8efe958d4ffdb9ce62b,https://investemoz.tempo.co.mz/frelimo-e-zanu-pf-evocados-lacos-de-irmandade,53,existente por integrar,inventariado; cadastro/versão por reconciliar,,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites.,legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados,,"Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,,historico_noticioso
62418,Governo dará todo O apoio à PGR no seguimento da auditoria,tempo,Categoria,2017-06-26T12:30:37+00:00,2017-06-26T12:30:45+00:00,65299b6711499e417be778b51c7ab7880616b0ccbf362854370eb775fc54caf3,https://investemoz.tempo.co.mz/governo-dara-todo-o-apoio-a-pgr-no-seguimento-da-auditoria,54,existente por integrar,inventariado; cadastro/versão por reconciliar,,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites.,legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados,,"Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,,historico_noticioso
75002,Governo pretende construir 100 mil habitações em dois anos,tempo,Nas Redes,2017-08-04T20:01:55+00:00,2017-08-04T20:01:55+00:00,b32ea95039c2af387687738ed601f0947384eb7e19ca4bd282d603c60ca26afa,https://investemoz.tempo.co.mz/governo-pretende-construir-100-mil-habitacoes-em-dois-anos,46,existente por integrar,inventariado; cadastro/versão por reconciliar,,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites.,legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados,,"Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,,historico_noticioso
173478,Guia Prático para Empregadores sobre Contratação e Direitos no Trabalho Doméstico,tempo,Trabalho Doméstico,2026-09-15T12:30:34+00:00,2026-09-15T12:30:37+00:00,8038d8c221ba3cbbc4712304a6b17a6bba8ad946c8bac1a7836d472944aaf930,https://investemoz.tempo.co.mz/guia-pratico-para-empregadores-sobre-contratacao-e-direitos-no-trabalho-domestico,65,existente por integrar,inventariado; cadastro/versão por reconciliar,,trabalho_domestico,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites.,legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados,,"Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,,trabalho_domestico
173222,Energia e Recursos Naturais,tempo,Oportunidades,2026-06-16T12:47:16+00:00,2026-06-16T12:51:30+00:00,6f8236b0441d61fce5171994ba6b82ffc8b5f2de3e8242de7be29248ed046ad2,https://investemoz.tempo.co.mz/energia-recursos-naturais,63,existente por integrar,inventariado; cadastro/versão por reconciliar,51,setorial,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,"Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos.",docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas,https://teste-investidor.tempo.co.mz/management/portal-services/resources/51/article/preview,"Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,,setorial
62414,Frelimo e Zanu-PF: Evocados laços de irmandade,tempo,Categoria,2017-06-26T12:03:20+00:00,2017-06-26T12:03:20+00:00,013307965b8fcb349a0d936d844ed12437bda4c90389f8efe958d4ffdb9ce62b,https://investemoz.tempo.co.mz/frelimo-e-zanu-pf-evocados-lacos-de-irmandade,53,existente por integrar,inventariado; cadastro/versão por reconciliar,35,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,"Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos.",docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas,https://teste-investidor.tempo.co.mz/management/portal-services/resources/35/article/preview,"Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,,historico_noticioso
62418,Governo dará todo O apoio à PGR no seguimento da auditoria,tempo,Categoria,2017-06-26T12:30:37+00:00,2017-06-26T12:30:45+00:00,65299b6711499e417be778b51c7ab7880616b0ccbf362854370eb775fc54caf3,https://investemoz.tempo.co.mz/governo-dara-todo-o-apoio-a-pgr-no-seguimento-da-auditoria,54,existente por integrar,inventariado; cadastro/versão por reconciliar,36,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,"Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos.",docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas,https://teste-investidor.tempo.co.mz/management/portal-services/resources/36/article/preview,"Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,,historico_noticioso
75002,Governo pretende construir 100 mil habitações em dois anos,tempo,Nas Redes,2017-08-04T20:01:55+00:00,2017-08-04T20:01:55+00:00,b32ea95039c2af387687738ed601f0947384eb7e19ca4bd282d603c60ca26afa,https://investemoz.tempo.co.mz/governo-pretende-construir-100-mil-habitacoes-em-dois-anos,46,existente por integrar,inventariado; cadastro/versão por reconciliar,37,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,"Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos.",docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas,https://teste-investidor.tempo.co.mz/management/portal-services/resources/37/article/preview,"Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,,historico_noticioso
173478,Guia Prático para Empregadores sobre Contratação e Direitos no Trabalho Doméstico,tempo,Trabalho Doméstico,2026-09-15T12:30:34+00:00,2026-09-15T12:30:37+00:00,8038d8c221ba3cbbc4712304a6b17a6bba8ad946c8bac1a7836d472944aaf930,https://investemoz.tempo.co.mz/guia-pratico-para-empregadores-sobre-contratacao-e-direitos-no-trabalho-domestico,65,existente por integrar,inventariado; cadastro/versão por reconciliar,54,trabalho_domestico,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,"Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos.",docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas,https://teste-investidor.tempo.co.mz/management/portal-services/resources/54/article/preview,"Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,,trabalho_domestico
173218,Imobiliário,tempo,Oportunidades,2026-06-16T11:34:45+00:00,2026-06-16T11:34:49+00:00,be8b606f087a939bd88a86f4436a0ff18c5fb774a179e6e915d4abb10a95f930,https://investemoz.tempo.co.mz/imobiliario,28,publicado,inventariado; cadastro/versão por reconciliar,7,publicados,"{""scope"": ""homologação"", ""href"": ""/documentos/27/view"", ""status"": ""referência na ficha; binário não reensaiado""}",Sem pendência de integração desta identidade; mantém-se a revisão editorial corrente.,Preservar a publicação; gerir edições e versões pela biblioteca existente.,outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado,https://teste-investidor.tempo.co.mz/imobiliario,Publicado; identidade e conteúdo consultáveis na homologação,Não foi feita aceitação visual ou conferência pela Tempo nesta passagem.,2026-10-22T21:05:03.369388+00:00,
171992,Infraestruturas,tempo,Oportunidades,2026-04-06T09:25:07+00:00,2026-04-30T12:44:37+00:00,9716da16aba58eec173343f0bbd582d62d23a5eee7d20522c080c2da5da8ba66,https://investemoz.tempo.co.mz/infraestruturas,22,publicado,RecursoQA22; original/hash/ClamD/replay demonstrados. DivulgaçãoQAseparada;consumidorHTTP/desktop observado;não é oportunidade nem geometria.,17,publicados,"{""scope"": ""homologação"", ""href"": ""/documentos/16/view"", ""status"": ""referência na ficha; binário não reensaiado""}",Sem pendência de integração desta identidade; mantém-se a revisão editorial corrente.,Preservar a publicação; gerir edições e versões pela biblioteca existente.,outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado,https://teste-investidor.tempo.co.mz/infraestruturas,Publicado; identidade e conteúdo consultáveis na homologação,Não foi feita aceitação visual ou conferência pela Tempo nesta passagem.,2026-10-22T21:05:03.369388+00:00,
172240,"Maputo Cidade – Plataforma Estratégica de Turismo, Negócios e Investimento Sustentável",tempo,Turismo,2026-04-14T10:58:53+00:00,2026-04-14T11:03:28+00:00,0866639d09c5c0b3b00e8527554065587c793f60df978b66b6a578b3c741bc0d,https://investemoz.tempo.co.mz/maputo-cidade-plataforma-estrategica-de-turismo-negocios-e-investimento-sustentavel/ | https://investemoz.tempo.co.mz/maputo-cidade-plataforma-estrategica-de-turismo-negocios-e-investimento-sustentavel,16,publicado,RecursoQA16; original/hash/ClamD/replay demonstrados. DivulgaçãoQAseparada;consumidorHTTP/desktop observado;não é oportunidade nem geometria.,21,publicados,"{""scope"": ""homologação"", ""href"": ""/documentos/27/view"", ""status"": ""referência na ficha; binário não reensaiado""}",Sem pendência de integração desta identidade; mantém-se a revisão editorial corrente.,Preservar a publicação; gerir edições e versões pela biblioteca existente.,outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado,https://teste-investidor.tempo.co.mz/maputo-cidade-plataforma-estrategica-de-turismo-negocios-e-investimento-sustentavel,Publicado; identidade e conteúdo consultáveis na homologação,Não foi feita aceitação visual ou conferência pela Tempo nesta passagem.,2026-10-22T21:05:03.369388+00:00,
171994,Mercado Financeiro,tempo,Oportunidades,2026-04-06T09:27:00+00:00,2026-04-17T09:53:42+00:00,e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855,https://investemoz.tempo.co.mz/mercado-financeiro,23,vazio — dependência concreta,"RecursoQA23; original/hash/ClamD/replay demonstrados. Corpo vazio na origemHTML/Inertia200;privado,sem página publicada.",,mercado_financeiro,"{""status"": ""capturada mas não publicada no ativo"", ""sha256"": ""417b4a33468f360acef272ce3e4180c1d0a171980befa73ddb9a33de5a9d4c4e""}",Corpo do artigo vazio; imagem não substitui conteúdo.,"Manter pendente e separado, sem bloquear outros lotes.",outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado,,Original vazio,Fornecer corpo editorial pela Tempo; imagem e Fintech não substituem o original. Sem novo endereço público.,,
173480,Modelo de Contrato de Trabalho Doméstico para Pequenas Famílias,tempo,Trabalho Doméstico,2026-09-15T12:34:17+00:00,2026-09-15T12:34:19+00:00,5b95ad9010fcbdc1000052f47766ca54409d3e0649211729ced9ef142f5c6db0,https://investemoz.tempo.co.mz/modelo-de-contrato-de-trabalho-domestico-para-pequenas-familias,66,existente por integrar,inventariado; cadastro/versão por reconciliar,,trabalho_domestico,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites.,legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados,,"Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,,trabalho_domestico
173480,Modelo de Contrato de Trabalho Doméstico para Pequenas Famílias,tempo,Trabalho Doméstico,2026-09-15T12:34:17+00:00,2026-09-15T12:34:19+00:00,5b95ad9010fcbdc1000052f47766ca54409d3e0649211729ced9ef142f5c6db0,https://investemoz.tempo.co.mz/modelo-de-contrato-de-trabalho-domestico-para-pequenas-familias,66,existente por integrar,inventariado; cadastro/versão por reconciliar,55,trabalho_domestico,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,"Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos.",docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas,https://teste-investidor.tempo.co.mz/management/portal-services/resources/55/article/preview,"Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,,trabalho_domestico
172219,Niassa - O último grande destino virgem do turismo africano,tempo,Turismo,2026-04-14T10:17:14+00:00,2026-04-14T10:43:56+00:00,47d82a59a86c3cbdef04ea065e711e7139d50137e440f03efbd710e3c4220ef7,https://investemoz.tempo.co.mz/niassa-o-ultimo-grande-destino-virgem-do-turismo-africano/ | https://investemoz.tempo.co.mz/niassa-o-ultimo-grande-destino-virgem-do-turismo-africano,21,publicado,RecursoQA21; original/hash/ClamD/replay demonstrados. DivulgaçãoQAseparada;consumidorHTTP/desktop observado;não é oportunidade nem geometria.,26,publicados,"{""scope"": ""homologação"", ""href"": ""/documentos/27/view"", ""status"": ""referência na ficha; binário não reensaiado""}",Sem pendência de integração desta identidade; mantém-se a revisão editorial corrente.,Preservar a publicação; gerir edições e versões pela biblioteca existente.,outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado,https://teste-investidor.tempo.co.mz/niassa-o-ultimo-grande-destino-virgem-do-turismo-africano,Publicado; identidade e conteúdo consultáveis na homologação,Não foi feita aceitação visual ou conferência pela Tempo nesta passagem.,2026-10-22T21:05:03.369388+00:00,
173476,Procedimentos para Rescisão do Contrato de Trabalho Doméstico segundo o Regulamento de Moçambique,tempo,Trabalho Doméstico,2026-09-15T11:39:21+00:00,2026-09-15T11:39:24+00:00,bb634a9021da9d7b46ed105317aa54e4af42a16dab042e0ceb2bdc0d702d1a67,https://investemoz.tempo.co.mz/procedimentos-para-rescisao-do-contrato-de-trabalho-domestico-segundo-o-regulamento-de-mocambique,67,existente por integrar,inventariado; cadastro/versão por reconciliar,,trabalho_domestico,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites.,legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados,,"Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,,trabalho_domestico
173476,Procedimentos para Rescisão do Contrato de Trabalho Doméstico segundo o Regulamento de Moçambique,tempo,Trabalho Doméstico,2026-09-15T11:39:21+00:00,2026-09-15T11:39:24+00:00,bb634a9021da9d7b46ed105317aa54e4af42a16dab042e0ceb2bdc0d702d1a67,https://investemoz.tempo.co.mz/procedimentos-para-rescisao-do-contrato-de-trabalho-domestico-segundo-o-regulamento-de-mocambique,67,existente por integrar,inventariado; cadastro/versão por reconciliar,56,trabalho_domestico,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,"Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos.",docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas,https://teste-investidor.tempo.co.mz/management/portal-services/resources/56/article/preview,"Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,,trabalho_domestico
172263,"Província da Zambézia: Onde os Corredores de Moçambique Convergem com a Natureza, a Cultura e o Turismo Sustentável",tempo,Turismo,2026-04-14T14:21:52+00:00,2026-04-14T14:26:27+00:00,9e7ca4e9542135d5969f5c008ce8e29b037cc06a53875cfa7dc3e7452ec47744,https://investemoz.tempo.co.mz/provincia-da-zambezia-onde-os-corredores-de-mocambique-convergem-com-a-natureza-a-cultura-e-o-turismo-sustentavel,11,publicado,RecursoQA11; original/hash/ClamD/replay demonstrados. DivulgaçãoQAseparada;consumidorHTTP/desktop observado;não é oportunidade nem geometria.,3,publicados,"{""scope"": ""homologação"", ""href"": ""/documentos/27/view"", ""status"": ""referência na ficha; binário não reensaiado""}",Sem pendência de integração desta identidade; mantém-se a revisão editorial corrente.,Preservar a publicação; gerir edições e versões pela biblioteca existente.,outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado,https://teste-investidor.tempo.co.mz/provincia-da-zambezia-onde-os-corredores-de-mocambique-convergem-com-a-natureza-a-cultura-e-o-turismo-sustentavel,Publicado; identidade e conteúdo consultáveis na homologação,Não foi feita aceitação visual ou conferência pela Tempo nesta passagem.,2026-10-22T21:05:03.369388+00:00,
172233,Província de Gaza: Oportunidades mais sólidas e estratégicas de investimento no turismo no sul de Moçambique.,tempo,Turismo,2026-04-14T10:52:30+00:00,2026-04-17T08:28:56+00:00,2bf2df9bd3b7ab8656209a48f4928b5d8bbf2959e77c292fc4c9987e67ba2fea,https://investemoz.tempo.co.mz/provincia-de-gaza-oportunidades-mais-solidas-e-estrategicas-de-investimento-no-turismo-no-sul-de-mocambique/ | https://investemoz.tempo.co.mz/provincia-de-gaza-oportunidades-mais-solidas-e-estrategicas-de-investimento-no-turismo-no-sul-de-mocambique,18,publicado,RecursoQA18; original/hash/ClamD/replay demonstrados. DivulgaçãoQAseparada;consumidorHTTP/desktop observado;não é oportunidade nem geometria.,23,publicados,"{""scope"": ""homologação"", ""href"": ""/documentos/27/view"", ""status"": ""referência na ficha; binário não reensaiado""}",Sem pendência de integração desta identidade; mantém-se a revisão editorial corrente.,Preservar a publicação; gerir edições e versões pela biblioteca existente.,outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado,https://teste-investidor.tempo.co.mz/provincia-de-gaza-oportunidades-mais-solidas-e-estrategicas-de-investimento-no-turismo-no-sul-de-mocambique,Publicado; identidade e conteúdo consultáveis na homologação,Não foi feita aceitação visual ou conferência pela Tempo nesta passagem.,2026-10-22T21:05:03.369388+00:00,
172253,Província de Inhambane: Um Destino Estratégico para Investimento Turístico,tempo,Turismo,2026-04-14T14:15:12+00:00,2026-04-14T14:25:44+00:00,2d1cc729481ce65a5e50898c0eea4a1eb2418273b03fc749009e2ffaaa72fe71,https://investemoz.tempo.co.mz/provincia-de-inhambane-um-destino-estrategico-para-investimento-turistico/ | https://investemoz.tempo.co.mz/provincia-de-inhambane-um-destino-estrategico-para-investimento-turistico,15,publicado,RecursoQA15; original/hash/ClamD/replay demonstrados. DivulgaçãoQAseparada;consumidorHTTP/desktop observado;não é oportunidade nem geometria.,6,publicados,"{""scope"": ""homologação"", ""href"": ""/documentos/27/view"", ""status"": ""referência na ficha; binário não reensaiado""}",Sem pendência de integração desta identidade; mantém-se a revisão editorial corrente.,Preservar a publicação; gerir edições e versões pela biblioteca existente.,outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado,https://teste-investidor.tempo.co.mz/provincia-de-inhambane-um-destino-estrategico-para-investimento-turistico,Publicado; identidade e conteúdo consultáveis na homologação,Não foi feita aceitação visual ou conferência pela Tempo nesta passagem.,2026-10-22T21:05:03.369388+00:00,
@@ -29,23 +29,23 @@
172230,Província de Nampula - Perfil de investimento no setor do turismo,tempo,Turismo,2026-04-14T10:50:32+00:00,2026-04-14T11:10:23+00:00,d18ccc1e036b3480605462ce7176be75153f6aa988bc9b37d8d395b01e555a7a,https://investemoz.tempo.co.mz/provincia-de-nampula-perfil-de-investimento-no-setor-do-turismo/ | https://investemoz.tempo.co.mz/provincia-de-nampula-perfil-de-investimento-no-setor-do-turismo,19,publicado,RecursoQA19; original/hash/ClamD/replay demonstrados. DivulgaçãoQAseparada;consumidorHTTP/desktop observado;não é oportunidade nem geometria.,24,publicados,"{""scope"": ""homologação"", ""href"": ""/documentos/27/view"", ""status"": ""referência na ficha; binário não reensaiado""}",Sem pendência de integração desta identidade; mantém-se a revisão editorial corrente.,Preservar a publicação; gerir edições e versões pela biblioteca existente.,outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado,https://teste-investidor.tempo.co.mz/provincia-de-nampula-perfil-de-investimento-no-setor-do-turismo,Publicado; identidade e conteúdo consultáveis na homologação,Não foi feita aceitação visual ou conferência pela Tempo nesta passagem.,2026-10-22T21:05:03.369388+00:00,
172256,"Província de Sofala: Onde a Natureza, a História e o Futuro do Turismo Convergem",tempo,Turismo,2026-04-14T14:16:47+00:00,2026-04-14T14:26:17+00:00,12b46a34ba61c6ef0058a6c410ebc17329d897fe4b06a13b4ea3fe3c85a28342,https://investemoz.tempo.co.mz/provincia-de-sofala-onde-a-natureza-a-historia-e-o-futuro-do-turismo-convergem/ | https://investemoz.tempo.co.mz/provincia-de-sofala-onde-a-natureza-a-historia-e-o-futuro-do-turismo-convergem,12,publicado,RecursoQA12; original/hash/ClamD/replay demonstrados. DivulgaçãoQAseparada;consumidorHTTP/desktop observado;não é oportunidade nem geometria.,4,publicados,"{""scope"": ""homologação"", ""href"": ""/documentos/27/view"", ""status"": ""referência na ficha; binário não reensaiado""}",Sem pendência de integração desta identidade; mantém-se a revisão editorial corrente.,Preservar a publicação; gerir edições e versões pela biblioteca existente.,outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado,https://teste-investidor.tempo.co.mz/provincia-de-sofala-onde-a-natureza-a-historia-e-o-futuro-do-turismo-convergem,Publicado; identidade e conteúdo consultáveis na homologação,Não foi feita aceitação visual ou conferência pela Tempo nesta passagem.,2026-10-22T21:05:03.369388+00:00,
172261,"Província de Tete: Onde o Rio Zambeze, a Cultura Viva e a Conservação Comunitária Criam o Futuro do Turismo",tempo,Turismo,2026-04-14T14:19:48+00:00,2026-04-14T14:26:25+00:00,e78fce2931260a7d6f82270897e906feb640f65d084c2d4ffb5ba19303858275,https://investemoz.tempo.co.mz/provincia-de-tete-onde-o-rio-zambeze-a-cultura-viva-e-a-conservacao-comunitaria-criam-o-futuro-do-turismo/ | https://investemoz.tempo.co.mz/provincia-de-tete-onde-o-rio-zambeze-a-cultura-viva-e-a-conservacao-comunitaria-criam-o-futuro-do-turismo,13,publicado,RecursoQA13; original/hash/ClamD/replay demonstrados. DivulgaçãoQAseparada;consumidorHTTP/desktop observado;não é oportunidade nem geometria.,5,publicados,"{""scope"": ""homologação"", ""href"": ""/documentos/27/view"", ""status"": ""referência na ficha; binário não reensaiado""}",Sem pendência de integração desta identidade; mantém-se a revisão editorial corrente.,Preservar a publicação; gerir edições e versões pela biblioteca existente.,outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado,https://teste-investidor.tempo.co.mz/provincia-de-tete-onde-o-rio-zambeze-a-cultura-viva-e-a-conservacao-comunitaria-criam-o-futuro-do-turismo,Publicado; identidade e conteúdo consultáveis na homologação,Não foi feita aceitação visual ou conferência pela Tempo nesta passagem.,2026-10-22T21:05:03.369388+00:00,
73246,Rofino Licuco preso cinco meses depois de condenado por agressão,tempo,Nas Redes,2017-07-28T20:23:11+00:00,2017-07-28T20:23:11+00:00,806c54948641f4f5933a2ea4a6935dfb7a67202ab227960451d62435880289f6,https://investemoz.tempo.co.mz/rofino-licuco-preso-cinco-meses-depois-de-condenado-por-agressao,47,existente por integrar,inventariado; cadastro/versão por reconciliar,,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites.,legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados,,"Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,,historico_noticioso
77232,Sete indivíduos mortos a tiro na Matola,tempo,Nas Redes,2017-08-11T20:30:32+00:00,2017-08-11T20:30:32+00:00,e657a97d5d6db7fa0ea6b7d14029ff873ea5d36dfdadda5f8b9bc8f1ef5dd011,https://investemoz.tempo.co.mz/sete-individuos-mortos-a-tiro-na-matola,48,existente por integrar,inventariado; cadastro/versão por reconciliar,,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites.,legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados,,"Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,,historico_noticioso
62416,Sete pessoas morrem e 150 são resgatados em acidente com eléctrico na Índia,tempo,Categoria,2017-06-26T12:14:48+00:00,2017-06-26T12:14:48+00:00,0fe20a9ca9dd99c58072f43b3dece1de347ef4063ee5f75bcffe8b2f4bf253b5,https://investemoz.tempo.co.mz/sete-pessoas-morrem-e-150-sao-resgatados-em-acidente-com-electrico-na-india,55,existente por integrar,inventariado; cadastro/versão por reconciliar,,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites.,legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados,,"Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,,historico_noticioso
73246,Rofino Licuco preso cinco meses depois de condenado por agressão,tempo,Nas Redes,2017-07-28T20:23:11+00:00,2017-07-28T20:23:11+00:00,806c54948641f4f5933a2ea4a6935dfb7a67202ab227960451d62435880289f6,https://investemoz.tempo.co.mz/rofino-licuco-preso-cinco-meses-depois-de-condenado-por-agressao,47,existente por integrar,inventariado; cadastro/versão por reconciliar,38,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,"Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos.",docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas,https://teste-investidor.tempo.co.mz/management/portal-services/resources/38/article/preview,"Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,,historico_noticioso
77232,Sete indivíduos mortos a tiro na Matola,tempo,Nas Redes,2017-08-11T20:30:32+00:00,2017-08-11T20:30:32+00:00,e657a97d5d6db7fa0ea6b7d14029ff873ea5d36dfdadda5f8b9bc8f1ef5dd011,https://investemoz.tempo.co.mz/sete-individuos-mortos-a-tiro-na-matola,48,existente por integrar,inventariado; cadastro/versão por reconciliar,39,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,"Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos.",docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas,https://teste-investidor.tempo.co.mz/management/portal-services/resources/39/article/preview,"Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,,historico_noticioso
62416,Sete pessoas morrem e 150 são resgatados em acidente com eléctrico na Índia,tempo,Categoria,2017-06-26T12:14:48+00:00,2017-06-26T12:14:48+00:00,0fe20a9ca9dd99c58072f43b3dece1de347ef4063ee5f75bcffe8b2f4bf253b5,https://investemoz.tempo.co.mz/sete-pessoas-morrem-e-150-sao-resgatados-em-acidente-com-electrico-na-india,55,existente por integrar,inventariado; cadastro/versão por reconciliar,40,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,"Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos.",docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas,https://teste-investidor.tempo.co.mz/management/portal-services/resources/40/article/preview,"Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,,historico_noticioso
173209,Fintech,tempo,Oportunidades,2026-06-16T11:24:22+00:00,2026-06-23T10:03:06+00:00,8db9206bf5192b68e7c9bdfb87e9dc9c7063a4b7eca56630685b0e6c294f55ab,https://investemoz.tempo.co.mz/tech-fintech,27,publicado,inventariado; cadastro/versão por reconciliar,28,publicados,"{""scope"": ""homologação"", ""href"": ""/documentos/27/view"", ""status"": ""referência na ficha; binário não reensaiado""}",Sem pendência de integração desta identidade; mantém-se a revisão editorial corrente.,Preservar a publicação; gerir edições e versões pela biblioteca existente.,outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado,https://teste-investidor.tempo.co.mz/tech-fintech,Publicado; identidade e conteúdo consultáveis na homologação,Não foi feita aceitação visual ou conferência pela Tempo nesta passagem.,2026-10-22T21:05:03.369388+00:00,
171990,Turismo,tempo,Oportunidades,2026-04-06T09:22:51+00:00,2026-06-16T13:07:59+00:00,604d30a740cc6ed20d24e3447806e733aa4aefb9f339f81d3dc57b644dd19fb7,https://investemoz.tempo.co.mz/turismo,10,publicado,RecursoQA10; original/hash/ClamD/replay demonstrados. DivulgaçãoQAseparada;consumidorHTTP/desktop observado;não é oportunidade nem geometria.,19,publicados,"{""scope"": ""homologação"", ""href"": ""/documentos/15/view"", ""status"": ""referência na ficha; binário não reensaiado""}",Sem pendência de integração desta identidade; mantém-se a revisão editorial corrente.,Preservar a publicação; gerir edições e versões pela biblioteca existente.,outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado,https://teste-investidor.tempo.co.mz/turismo,Publicado; identidade e conteúdo consultáveis na homologação,Não foi feita aceitação visual ou conferência pela Tempo nesta passagem.,2026-10-22T21:05:03.369388+00:00,
74364,,tempo,Categoria,2017-07-26T21:20:33+00:00,2017-07-26T21:20:33+00:00,2a7f0a5b09e0136fec9eae357ac8a88a159ead23b25ed7bb735c75997b10ec5e,https://investemoz.tempo.co.mz/74364-2,,dependência concreta — título ausente,inventariado; cadastro/versão por reconciliar,,identidade_incompleta,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Referência não recolhida: http://www.newsaiep.com/moz_news/wp-content/uploads/1501695692_133_notitle.jpg | Referência não recolhida: https://www.facebook.com/179463555444013/posts/1552815684775453 | Título vazio no post 74364; corpo com 20 caracteres; identidade editorial a esclarecer sem inventar título.,Preservar exemplar e referências; delimitar recuperação editorial antes de integrar.,outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado,,Original sem título,Identificação editorial pela Tempo para o post 74364; corpo com 21 caracteres úteis preservado. Sem título ou endereço novo inventado.,,
62929,Daúde Razaque já não é treinador da Liga Desportiva de Maputo,tempo,Nas Redes,2017-06-27T16:41:43+00:00,2017-06-27T16:41:43+00:00,c3b57ea685290bfb01c50403bc83db98cf1a42ad51c04d5b8d288c99b8c0a808,https://investemoz.tempo.co.mz/daude-razaque-ja-nao-e-treinador-da-liga-desportiva-de-maputo,49,existente por integrar,inventariado; cadastro/versão por reconciliar,,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites.,legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados,,"Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,,historico_noticioso
62649,Daviz Simango não se incomoda com possível sucessão na Beira,tempo,Nas Redes,2017-06-26T19:38:09+00:00,2017-06-26T19:38:09+00:00,090e5f41ed11c8e680d2e30ea5c8310b7c6b165ddec318a8b0cf94927f78da74,https://investemoz.tempo.co.mz/daviz-simango-nao-se-incomoda-com-possivel-sucessao-na-beira,56,existente por integrar,inventariado; cadastro/versão por reconciliar,,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites.,legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados,,"Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,,historico_noticioso
173470,Direitos e Deveres do Trabalhador Doméstico,tempo,Trabalho Doméstico,2026-09-15T11:28:25+00:00,2026-09-15T11:28:28+00:00,f69fd6fd8509913f2d79df0267c469dc7a9d0d30420ce8428749d53ba21446ab,https://investemoz.tempo.co.mz/direitos-e-deveres-do-trabalhador-domestico,68,existente por integrar,inventariado; cadastro/versão por reconciliar,,trabalho_domestico,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites.,legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados,,"Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,,trabalho_domestico
62929,Daúde Razaque já não é treinador da Liga Desportiva de Maputo,tempo,Nas Redes,2017-06-27T16:41:43+00:00,2017-06-27T16:41:43+00:00,c3b57ea685290bfb01c50403bc83db98cf1a42ad51c04d5b8d288c99b8c0a808,https://investemoz.tempo.co.mz/daude-razaque-ja-nao-e-treinador-da-liga-desportiva-de-maputo,49,existente por integrar,inventariado; cadastro/versão por reconciliar,41,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,"Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos.",docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas,https://teste-investidor.tempo.co.mz/management/portal-services/resources/41/article/preview,"Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,,historico_noticioso
62649,Daviz Simango não se incomoda com possível sucessão na Beira,tempo,Nas Redes,2017-06-26T19:38:09+00:00,2017-06-26T19:38:09+00:00,090e5f41ed11c8e680d2e30ea5c8310b7c6b165ddec318a8b0cf94927f78da74,https://investemoz.tempo.co.mz/daviz-simango-nao-se-incomoda-com-possivel-sucessao-na-beira,56,existente por integrar,inventariado; cadastro/versão por reconciliar,42,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,"Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos.",docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas,https://teste-investidor.tempo.co.mz/management/portal-services/resources/42/article/preview,"Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,,historico_noticioso
173470,Direitos e Deveres do Trabalhador Doméstico,tempo,Trabalho Doméstico,2026-09-15T11:28:25+00:00,2026-09-15T11:28:28+00:00,f69fd6fd8509913f2d79df0267c469dc7a9d0d30420ce8428749d53ba21446ab,https://investemoz.tempo.co.mz/direitos-e-deveres-do-trabalhador-domestico,68,existente por integrar,inventariado; cadastro/versão por reconciliar,57,trabalho_domestico,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,"Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos.",docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas,https://teste-investidor.tempo.co.mz/management/portal-services/resources/57/article/preview,"Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,,trabalho_domestico
172679,Energia,tempo,Featured,2026-04-30T13:03:57+00:00,2026-04-30T13:28:48+00:00,e2a7eb4be0a2cf4139737f6428c147ac35de99899478ce79195dc5e1b0a960f3,https://investemoz.tempo.co.mz/energia | https://investemoz.tempo.co.mz/energia/,8,publicado,RecursoQA8; original/hash/ClamD/replay demonstrados. DivulgaçãoQAseparada;consumidorHTTP/desktop observado;não é oportunidade nem geometria.,1,publicados,"{""scope"": ""homologação"", ""href"": ""/documentos/27/view"", ""status"": ""referência na ficha; binário não reensaiado""}",Sem pendência de integração desta identidade; mantém-se a revisão editorial corrente.,Preservar a publicação; gerir edições e versões pela biblioteca existente.,outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado,https://teste-investidor.tempo.co.mz/energia,Publicado; identidade e conteúdo consultáveis na homologação,Não foi feita aceitação visual ou conferência pela Tempo nesta passagem.,2026-10-22T21:05:03.369388+00:00,
62739,"“Exigimos responsabilização dos agentes do crime”, Daviz Simango",tempo,Nas Redes,2017-06-26T23:13:36+00:00,2017-06-26T23:13:36+00:00,e37ea18176246dab529d2ecec7734f0fb2b0a54cadc507fbd789033d5b8e5b93,https://investemoz.tempo.co.mz/exigimos-responsabilizacao-dos-agentes-do-crime-daviz-simango-2,57,existente por integrar,inventariado; cadastro/versão por reconciliar,,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites.,legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados,,"Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,,historico_noticioso
62854,Governo e parceiros reforçam mecanismos de controlo de HIV/Sida,tempo,Nas Redes,2017-06-27T11:07:53+00:00,2017-06-27T11:07:53+00:00,0073816b1fa4bd50ec6417b07272da06565230d06a19079ca76cb006c882d2f1,https://investemoz.tempo.co.mz/governo-e-parceiros-reforcam-mecanismos-de-controlo-de-hivsida,58,existente por integrar,inventariado; cadastro/versão por reconciliar,,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites.,legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados,,"Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,,historico_noticioso
63128,Graça Machel diz que desnutrição é um problema de desenvolvimento,tempo,Nas Redes,2017-06-27T22:01:59+00:00,2017-06-27T22:01:59+00:00,711f3707e3a0d72ec919216fc50823de9060349104ee98fe0a6b20d6684b6031,https://investemoz.tempo.co.mz/graca-machel-diz-que-desnutricao-e-um-problema-de-desenvolvimento,59,existente por integrar,inventariado; cadastro/versão por reconciliar,,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites.,legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados,,"Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,,historico_noticioso
63181,In dependência,tempo,Nas Redes,2017-06-27T21:38:06+00:00,2017-06-27T21:38:06+00:00,b0d87c0d58d6d1343d102cd3141bda1215c5afd54f3780a1c6c262d786dbfe27,https://investemoz.tempo.co.mz/in-dependencia,60,existente por integrar,inventariado; cadastro/versão por reconciliar,,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites.,legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados,,"Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,,historico_noticioso
63146,"Kroll não descobriu como foram gastos 2 biliões de dólares; Governo de Nyusi não apoiou a Auditoria; criação da Proindicus, EMATUM e MAM não foi para proteção da Zona Económica Exclusiva",tempo,Nas Redes,2017-06-27T19:30:01+00:00,2017-06-27T19:30:01+00:00,4ccbad5c92ddc3884f58181a4f1ed3c1d3d6a59f005bfd99b197a6fc1fc3c280,https://investemoz.tempo.co.mz/kroll-nao-descobriu-como-foram-gastos-2-bilioes-de-dolares-governo-de-nyusi-nao-apoiou-a-auditoria-criacao-da-proindicus-ematum-e-mam-nao-foi-para-protecao-da-zona-economica-exclusiva-2,61,existente por integrar,inventariado; cadastro/versão por reconciliar,,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites.,legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados,,"Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,,historico_noticioso
62739,"“Exigimos responsabilização dos agentes do crime”, Daviz Simango",tempo,Nas Redes,2017-06-26T23:13:36+00:00,2017-06-26T23:13:36+00:00,e37ea18176246dab529d2ecec7734f0fb2b0a54cadc507fbd789033d5b8e5b93,https://investemoz.tempo.co.mz/exigimos-responsabilizacao-dos-agentes-do-crime-daviz-simango-2,57,existente por integrar,inventariado; cadastro/versão por reconciliar,43,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,"Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos.",docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas,https://teste-investidor.tempo.co.mz/management/portal-services/resources/43/article/preview,"Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,,historico_noticioso
62854,Governo e parceiros reforçam mecanismos de controlo de HIV/Sida,tempo,Nas Redes,2017-06-27T11:07:53+00:00,2017-06-27T11:07:53+00:00,0073816b1fa4bd50ec6417b07272da06565230d06a19079ca76cb006c882d2f1,https://investemoz.tempo.co.mz/governo-e-parceiros-reforcam-mecanismos-de-controlo-de-hivsida,58,existente por integrar,inventariado; cadastro/versão por reconciliar,44,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,"Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos.",docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas,https://teste-investidor.tempo.co.mz/management/portal-services/resources/44/article/preview,"Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,,historico_noticioso
63128,Graça Machel diz que desnutrição é um problema de desenvolvimento,tempo,Nas Redes,2017-06-27T22:01:59+00:00,2017-06-27T22:01:59+00:00,711f3707e3a0d72ec919216fc50823de9060349104ee98fe0a6b20d6684b6031,https://investemoz.tempo.co.mz/graca-machel-diz-que-desnutricao-e-um-problema-de-desenvolvimento,59,existente por integrar,inventariado; cadastro/versão por reconciliar,45,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,"Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos.",docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas,https://teste-investidor.tempo.co.mz/management/portal-services/resources/45/article/preview,"Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,,historico_noticioso
63181,In dependência,tempo,Nas Redes,2017-06-27T21:38:06+00:00,2017-06-27T21:38:06+00:00,b0d87c0d58d6d1343d102cd3141bda1215c5afd54f3780a1c6c262d786dbfe27,https://investemoz.tempo.co.mz/in-dependencia,60,existente por integrar,inventariado; cadastro/versão por reconciliar,46,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,"Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos.",docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas,https://teste-investidor.tempo.co.mz/management/portal-services/resources/46/article/preview,"Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,,historico_noticioso
63146,"Kroll não descobriu como foram gastos 2 biliões de dólares; Governo de Nyusi não apoiou a Auditoria; criação da Proindicus, EMATUM e MAM não foi para proteção da Zona Económica Exclusiva",tempo,Nas Redes,2017-06-27T19:30:01+00:00,2017-06-27T19:30:01+00:00,4ccbad5c92ddc3884f58181a4f1ed3c1d3d6a59f005bfd99b197a6fc1fc3c280,https://investemoz.tempo.co.mz/kroll-nao-descobriu-como-foram-gastos-2-bilioes-de-dolares-governo-de-nyusi-nao-apoiou-a-auditoria-criacao-da-proindicus-ematum-e-mam-nao-foi-para-protecao-da-zona-economica-exclusiva-2,61,existente por integrar,inventariado; cadastro/versão por reconciliar,47,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,"Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos.",docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas,https://teste-investidor.tempo.co.mz/management/portal-services/resources/47/article/preview,"Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,,historico_noticioso
172674,Mineração,tempo,Featured,2026-04-30T13:12:09+00:00,2026-04-30T13:30:13+00:00,8d3acc292ed806bc4cc7bcb6a3a6a5759f30b610766a5ef4b3d6a0b7c56d5861,https://investemoz.tempo.co.mz/mineracao,9,publicado,RecursoQA9; original/hash/ClamD/replay demonstrados. DivulgaçãoQAseparada;consumidorHTTP/desktop observado;não é oportunidade nem geometria.,2,publicados,"{""scope"": ""homologação"", ""href"": ""/documentos/27/view"", ""status"": ""referência na ficha; binário não reensaiado""}",Sem pendência de integração desta identidade; mantém-se a revisão editorial corrente.,Preservar a publicação; gerir edições e versões pela biblioteca existente.,outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado,https://teste-investidor.tempo.co.mz/mineracao,Publicado; identidade e conteúdo consultáveis na homologação,Não foi feita aceitação visual ou conferência pela Tempo nesta passagem.,2026-10-22T21:05:03.369388+00:00,
62688,O Instituto Nacional de Meteorologia prevê o seguinte estado do tempo para esta ...,tempo,Nas Redes,2017-06-26T18:21:18+00:00,2017-06-26T18:21:18+00:00,396fe2575641622015505eb11d4c7152b6e3e1b741966f14c0cd1673fd455fd3,https://investemoz.tempo.co.mz/o-instituto-nacional-de-meteorologia-preve-o-seguinte-estado-do-tempo-para-esta-29,62,existente por integrar,inventariado; cadastro/versão por reconciliar,,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites.,legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados,,"Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,,historico_noticioso
173474,Sanções Disciplinares Aplicáveis a Trabalhadores Domésticos segundo o Regulamento de Moçambique,tempo,Trabalho Doméstico,2026-09-15T11:37:04+00:00,2026-09-15T11:37:06+00:00,e09a0fe71397e2b060ff8435111117aa487b476a85ec522fffcdf9e23a6cdac2,https://investemoz.tempo.co.mz/sancoes-disciplinares-aplicaveis-a-trabalhadores-domesticos-segundo-o-regulamento-de-mocambique,69,existente por integrar,inventariado; cadastro/versão por reconciliar,,trabalho_domestico,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites.,legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados,,"Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,,trabalho_domestico
63248,Dois alunos da 9ª classe caíram nas mãos da Polícia da República de Moçambique (...,tempo,Nas Redes,2017-06-28T11:30:01+00:00,2017-06-28T11:30:01+00:00,2220ca2c07d10f10b2bd6fd0f103ee733b83570d436c751c449c85b42df46a13,https://investemoz.tempo.co.mz/dois-alunos-da-9a-classe-cairam-nas-maos-da-policia-da-republica-de-mocambique,50,existente por integrar,inventariado; cadastro/versão por reconciliar,,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites.,legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados,,"Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,,historico_noticioso
173463,Regulamento do Trabalho Doméstico,tempo,Trabalho Doméstico,2026-09-15T10:56:07+00:00,2026-09-15T12:41:44+00:00,eb43b6c1f097631250f0b36d3d01b4d106db2fcae07a56bb154819143fff11e9,https://investemoz.tempo.co.mz/regulamento-do-trabalho-domestico,70,existente por integrar,inventariado; cadastro/versão por reconciliar,,trabalho_domestico,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites.,legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados,,"Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada",Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso.,,trabalho_domestico
62688,O Instituto Nacional de Meteorologia prevê o seguinte estado do tempo para esta ...,tempo,Nas Redes,2017-06-26T18:21:18+00:00,2017-06-26T18:21:18+00:00,396fe2575641622015505eb11d4c7152b6e3e1b741966f14c0cd1673fd455fd3,https://investemoz.tempo.co.mz/o-instituto-nacional-de-meteorologia-preve-o-seguinte-estado-do-tempo-para-esta-29,62,existente por integrar,inventariado; cadastro/versão por reconciliar,48,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,"Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos.",docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas,https://teste-investidor.tempo.co.mz/management/portal-services/resources/48/article/preview,"Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,,historico_noticioso
173474,Sanções Disciplinares Aplicáveis a Trabalhadores Domésticos segundo o Regulamento de Moçambique,tempo,Trabalho Doméstico,2026-09-15T11:37:04+00:00,2026-09-15T11:37:06+00:00,e09a0fe71397e2b060ff8435111117aa487b476a85ec522fffcdf9e23a6cdac2,https://investemoz.tempo.co.mz/sancoes-disciplinares-aplicaveis-a-trabalhadores-domesticos-segundo-o-regulamento-de-mocambique,69,existente por integrar,inventariado; cadastro/versão por reconciliar,58,trabalho_domestico,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,"Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos.",docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas,https://teste-investidor.tempo.co.mz/management/portal-services/resources/58/article/preview,"Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,,trabalho_domestico
63248,Dois alunos da 9ª classe caíram nas mãos da Polícia da República de Moçambique (...,tempo,Nas Redes,2017-06-28T11:30:01+00:00,2017-06-28T11:30:01+00:00,2220ca2c07d10f10b2bd6fd0f103ee733b83570d436c751c449c85b42df46a13,https://investemoz.tempo.co.mz/dois-alunos-da-9a-classe-cairam-nas-maos-da-policia-da-republica-de-mocambique,50,existente por integrar,inventariado; cadastro/versão por reconciliar,49,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,"Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos.",docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas,https://teste-investidor.tempo.co.mz/management/portal-services/resources/49/article/preview,"Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,,historico_noticioso
173463,Regulamento do Trabalho Doméstico,tempo,Trabalho Doméstico,2026-09-15T10:56:07+00:00,2026-09-15T12:41:44+00:00,eb43b6c1f097631250f0b36d3d01b4d106db2fcae07a56bb154819143fff11e9,https://investemoz.tempo.co.mz/regulamento-do-trabalho-domestico,70,existente por integrar,inventariado; cadastro/versão por reconciliar,59,trabalho_domestico,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,"Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos.",docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas,https://teste-investidor.tempo.co.mz/management/portal-services/resources/59/article/preview,"Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos",Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.,,trabalho_domestico
1 ID legado Título Autor Categoria Publicado Modificado SHA256 conteúdo URLs observadas Recurso QA Situação Situação anterior à reconciliação 15/10/2026 Recurso ativo Grupo restante Imagem atual Dependência concreta Próxima ação Evidência atual Endereço no novo portal Estado actual consolidado Pendência actual Conferência HTTP UTC Grupo de preparação QA
2 171985 Agronegocio tempo Oportunidades 2026-04-06T09:19:07+00:00 2026-04-20T12:41:43+00:00 28383a11d95f13894381357d8f1227b33031d1d3ff74507da4ea8e5ce2470913 https://investemoz.tempo.co.mz/agronegocio 6 publicado recurso único QA; não é oportunidade 13 publicados {"scope": "homologação", "href": "/documentos/12/view", "status": "referência na ficha; binário não reensaiado"} Sem pendência de integração desta identidade; mantém-se a revisão editorial corrente. Preservar a publicação; gerir edições e versões pela biblioteca existente. outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado https://teste-investidor.tempo.co.mz/agronegocio Publicado; identidade e conteúdo consultáveis na homologação Não foi feita aceitação visual ou conferência pela Tempo nesta passagem. 2026-10-22T21:05:03.369388+00:00
3 62413 15/30 costa do sol com dua na zona de mupulene muito lindo tempo Categoria 2017-06-26T11:44:21+00:00 2017-06-26T11:44:21+00:00 c04ae821317ef556c01c3141ce75eeeeb5da7787e0c6849b1b3709b757edb9eb https://investemoz.tempo.co.mz/1530-costa-do-sol-com-dua-na-zona-de-mupulene-muito-lindo 43 existente por integrar inventariado; cadastro/versão por reconciliar 32 historico_noticioso {"status": "imagem já publicada; reutilizar", "resource_id": 24, "sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"} Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Rever/corrigir a nota de demonstração QA antes de publicar. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. Rever/corrigir a nota de demonstração QA antes de publicar. Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites. Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos. legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas https://teste-investidor.tempo.co.mz/management/portal-services/resources/32/article/preview Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Rever/corrigir a nota de demonstração QA antes de publicar. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. Rever/corrigir a nota de demonstração QA antes de publicar. historico_noticioso
4 173220 Alumínio “Verde” tempo Oportunidades 2026-06-16T12:41:02+00:00 2026-06-16T12:41:06+00:00 c61f05a56bb5a5f13d9d5d54dbefd270cf9ac40320917d77aaad76cac56c7b97 https://investemoz.tempo.co.mz/aluminio-verde 51 existente por integrar inventariado; cadastro/versão por reconciliar 50 setorial {"status": "imagem já publicada; reutilizar", "resource_id": 24, "sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"} Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites. Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos. legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas https://teste-investidor.tempo.co.mz/management/portal-services/resources/50/article/preview Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. setorial
5 73945 Banco Mundial classifica de proibitivos juros praticados em Moçambique tempo Nas Redes 2017-07-31T19:55:24+00:00 2017-07-31T19:55:24+00:00 36ba82b8885b00a4ea2fd9e4b1d8cf304a8e78e591972682f0aa53717f648d19 https://investemoz.tempo.co.mz/banco-mundial-classifica-de-proibitivos-juros-praticados-em-mocambique 44 existente por integrar inventariado; cadastro/versão por reconciliar 33 historico_noticioso {"status": "imagem já publicada; reutilizar", "resource_id": 24, "sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"} Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites. Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos. legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas https://teste-investidor.tempo.co.mz/management/portal-services/resources/33/article/preview Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. historico_noticioso
6 173213 Bens de Consumo e Franquias tempo Oportunidades 2026-06-16T11:31:06+00:00 2026-06-16T11:32:10+00:00 abd5f36a0c2b89d2989334137aae5d59b3158da38d556013ccd55deaa5069e92 https://investemoz.tempo.co.mz/bens-de-consumo-franquias 29 publicado inventariado; cadastro/versão por reconciliar 29 publicados {"scope": "homologação", "href": "/documentos/27/view", "status": "referência na ficha; binário não reensaiado"} Sem pendência de integração desta identidade; mantém-se a revisão editorial corrente. Preservar a publicação; gerir edições e versões pela biblioteca existente. outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado https://teste-investidor.tempo.co.mz/bens-de-consumo-franquias Publicado; identidade e conteúdo consultáveis na homologação Não foi feita aceitação visual ou conferência pela Tempo nesta passagem. 2026-10-22T21:05:03.369388+00:00
7 172223 Cabo Delgado - Pemba, A capital do turismo costeiro do norte de Moçambique tempo Turismo 2026-04-14T10:21:20+00:00 2026-04-14T10:41:05+00:00 c96b2430c44b8edc9cc1d0a7974c23290dd0207cff366fea8326bb28bcab8285 https://investemoz.tempo.co.mz/cabo-delgado-pemba-a-capital-do-turismo-costeiro-do-norte-de-mocambique/ | https://investemoz.tempo.co.mz/cabo-delgado-pemba-a-capital-do-turismo-costeiro-do-norte-de-mocambique 20 publicado RecursoQA20; original/hash/ClamD/replay demonstrados. DivulgaçãoQAseparada;consumidorHTTP/desktop observado;não é oportunidade nem geometria. 25 publicados {"scope": "homologação", "href": "/documentos/27/view", "status": "referência na ficha; binário não reensaiado"} Sem pendência de integração desta identidade; mantém-se a revisão editorial corrente. Preservar a publicação; gerir edições e versões pela biblioteca existente. outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado https://teste-investidor.tempo.co.mz/cabo-delgado-pemba-a-capital-do-turismo-costeiro-do-norte-de-mocambique Publicado; identidade e conteúdo consultáveis na homologação Não foi feita aceitação visual ou conferência pela Tempo nesta passagem. 2026-10-22T21:05:03.369388+00:00
8 173482 Checklist para Contratação e Direitos do Trabalho Doméstico tempo Trabalho Doméstico 2026-09-15T12:36:11+00:00 2026-09-15T12:36:14+00:00 309b51ebc53bf840f9014da733bc09f8d162138be4e6e9d5a72670e26cc8e482 https://investemoz.tempo.co.mz/checklist-para-contratacao-e-direitos-do-trabalho-domestico 52 existente por integrar inventariado; cadastro/versão por reconciliar 52 trabalho_domestico {"status": "imagem já publicada; reutilizar", "resource_id": 24, "sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"} Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites. Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos. legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas https://teste-investidor.tempo.co.mz/management/portal-services/resources/52/article/preview Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. trabalho_domestico
9 76474 Coloque o seu número para o grupo de whatsapp, venda de acessórios de computador tempo Categoria 2017-08-08T17:29:51+00:00 2017-08-08T17:29:51+00:00 c8beeb94f936b3bfc69c720770bd84376e9f19b668d96185aaefbada05727010 https://investemoz.tempo.co.mz/coloque-o-seu-numero-para-o-grupo-de-whatsapp-venda-de-acessorios-de-computador 45 existente por integrar inventariado; cadastro/versão por reconciliar 34 historico_noticioso {"status": "imagem já publicada; reutilizar", "resource_id": 24, "sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"} Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites. Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos. legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas https://teste-investidor.tempo.co.mz/management/portal-services/resources/34/article/preview Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. historico_noticioso
10 173484 Direitos Especiais da Trabalhadora Doméstica no Regulamento tempo Trabalho Doméstico 2026-09-15T12:38:08+00:00 2026-09-15T12:38:11+00:00 34c31bb02a3ad0886f12ac2853283de2ffb89a925be3cda73b7ce9385c6caec4 https://investemoz.tempo.co.mz/direitos-especiais-da-trabalhadora-domestica-no-regulamento 64 existente por integrar inventariado; cadastro/versão por reconciliar 53 trabalho_domestico {"status": "imagem já publicada; reutilizar", "resource_id": 24, "sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"} Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites. Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos. legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas https://teste-investidor.tempo.co.mz/management/portal-services/resources/53/article/preview Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. trabalho_domestico
11 171981 Energia & Mineração tempo Oportunidades 2026-04-06T09:17:46+00:00 2026-04-30T13:35:32+00:00 e9a95f0c8176eea48a75bd2c5bce69f15308806988fa5203a688af7c40305e9c https://investemoz.tempo.co.mz/energia-mineracao 7 publicado RecursoQA7; original/hash/ClamD/replay demonstrados. DivulgaçãoQAseparada;consumidorHTTP/desktop observado;não é oportunidade nem geometria. 18 publicados {"scope": "homologação", "href": "/documentos/14/view", "status": "referência na ficha; binário não reensaiado"} Sem pendência de integração desta identidade; mantém-se a revisão editorial corrente. Preservar a publicação; gerir edições e versões pela biblioteca existente. outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado https://teste-investidor.tempo.co.mz/energia-mineracao Publicado; identidade e conteúdo consultáveis na homologação Não foi feita aceitação visual ou conferência pela Tempo nesta passagem. 2026-10-22T21:05:03.369388+00:00
12 173222 Energia e Recursos Naturais tempo Oportunidades 2026-06-16T12:47:16+00:00 2026-06-16T12:51:30+00:00 6f8236b0441d61fce5171994ba6b82ffc8b5f2de3e8242de7be29248ed046ad2 https://investemoz.tempo.co.mz/energia-recursos-naturais 63 existente por integrar inventariado; cadastro/versão por reconciliar 51 setorial {"status": "imagem já publicada; reutilizar", "resource_id": 24, "sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"} Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites. Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos. legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas https://teste-investidor.tempo.co.mz/management/portal-services/resources/51/article/preview Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. setorial
13 62414 Frelimo e Zanu-PF: Evocados laços de irmandade tempo Categoria 2017-06-26T12:03:20+00:00 2017-06-26T12:03:20+00:00 013307965b8fcb349a0d936d844ed12437bda4c90389f8efe958d4ffdb9ce62b https://investemoz.tempo.co.mz/frelimo-e-zanu-pf-evocados-lacos-de-irmandade 53 existente por integrar inventariado; cadastro/versão por reconciliar 35 historico_noticioso {"status": "imagem já publicada; reutilizar", "resource_id": 24, "sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"} Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites. Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos. legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas https://teste-investidor.tempo.co.mz/management/portal-services/resources/35/article/preview Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. historico_noticioso
14 62418 Governo dará todo O apoio à PGR no seguimento da auditoria tempo Categoria 2017-06-26T12:30:37+00:00 2017-06-26T12:30:45+00:00 65299b6711499e417be778b51c7ab7880616b0ccbf362854370eb775fc54caf3 https://investemoz.tempo.co.mz/governo-dara-todo-o-apoio-a-pgr-no-seguimento-da-auditoria 54 existente por integrar inventariado; cadastro/versão por reconciliar 36 historico_noticioso {"status": "imagem já publicada; reutilizar", "resource_id": 24, "sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"} Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites. Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos. legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas https://teste-investidor.tempo.co.mz/management/portal-services/resources/36/article/preview Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. historico_noticioso
15 75002 Governo pretende construir 100 mil habitações em dois anos tempo Nas Redes 2017-08-04T20:01:55+00:00 2017-08-04T20:01:55+00:00 b32ea95039c2af387687738ed601f0947384eb7e19ca4bd282d603c60ca26afa https://investemoz.tempo.co.mz/governo-pretende-construir-100-mil-habitacoes-em-dois-anos 46 existente por integrar inventariado; cadastro/versão por reconciliar 37 historico_noticioso {"status": "imagem já publicada; reutilizar", "resource_id": 24, "sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"} Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites. Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos. legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas https://teste-investidor.tempo.co.mz/management/portal-services/resources/37/article/preview Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. historico_noticioso
16 173478 Guia Prático para Empregadores sobre Contratação e Direitos no Trabalho Doméstico tempo Trabalho Doméstico 2026-09-15T12:30:34+00:00 2026-09-15T12:30:37+00:00 8038d8c221ba3cbbc4712304a6b17a6bba8ad946c8bac1a7836d472944aaf930 https://investemoz.tempo.co.mz/guia-pratico-para-empregadores-sobre-contratacao-e-direitos-no-trabalho-domestico 65 existente por integrar inventariado; cadastro/versão por reconciliar 54 trabalho_domestico {"status": "imagem já publicada; reutilizar", "resource_id": 24, "sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"} Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites. Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos. legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas https://teste-investidor.tempo.co.mz/management/portal-services/resources/54/article/preview Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. trabalho_domestico
17 173218 Imobiliário tempo Oportunidades 2026-06-16T11:34:45+00:00 2026-06-16T11:34:49+00:00 be8b606f087a939bd88a86f4436a0ff18c5fb774a179e6e915d4abb10a95f930 https://investemoz.tempo.co.mz/imobiliario 28 publicado inventariado; cadastro/versão por reconciliar 7 publicados {"scope": "homologação", "href": "/documentos/27/view", "status": "referência na ficha; binário não reensaiado"} Sem pendência de integração desta identidade; mantém-se a revisão editorial corrente. Preservar a publicação; gerir edições e versões pela biblioteca existente. outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado https://teste-investidor.tempo.co.mz/imobiliario Publicado; identidade e conteúdo consultáveis na homologação Não foi feita aceitação visual ou conferência pela Tempo nesta passagem. 2026-10-22T21:05:03.369388+00:00
18 171992 Infraestruturas tempo Oportunidades 2026-04-06T09:25:07+00:00 2026-04-30T12:44:37+00:00 9716da16aba58eec173343f0bbd582d62d23a5eee7d20522c080c2da5da8ba66 https://investemoz.tempo.co.mz/infraestruturas 22 publicado RecursoQA22; original/hash/ClamD/replay demonstrados. DivulgaçãoQAseparada;consumidorHTTP/desktop observado;não é oportunidade nem geometria. 17 publicados {"scope": "homologação", "href": "/documentos/16/view", "status": "referência na ficha; binário não reensaiado"} Sem pendência de integração desta identidade; mantém-se a revisão editorial corrente. Preservar a publicação; gerir edições e versões pela biblioteca existente. outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado https://teste-investidor.tempo.co.mz/infraestruturas Publicado; identidade e conteúdo consultáveis na homologação Não foi feita aceitação visual ou conferência pela Tempo nesta passagem. 2026-10-22T21:05:03.369388+00:00
19 172240 Maputo Cidade – Plataforma Estratégica de Turismo, Negócios e Investimento Sustentável tempo Turismo 2026-04-14T10:58:53+00:00 2026-04-14T11:03:28+00:00 0866639d09c5c0b3b00e8527554065587c793f60df978b66b6a578b3c741bc0d https://investemoz.tempo.co.mz/maputo-cidade-plataforma-estrategica-de-turismo-negocios-e-investimento-sustentavel/ | https://investemoz.tempo.co.mz/maputo-cidade-plataforma-estrategica-de-turismo-negocios-e-investimento-sustentavel 16 publicado RecursoQA16; original/hash/ClamD/replay demonstrados. DivulgaçãoQAseparada;consumidorHTTP/desktop observado;não é oportunidade nem geometria. 21 publicados {"scope": "homologação", "href": "/documentos/27/view", "status": "referência na ficha; binário não reensaiado"} Sem pendência de integração desta identidade; mantém-se a revisão editorial corrente. Preservar a publicação; gerir edições e versões pela biblioteca existente. outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado https://teste-investidor.tempo.co.mz/maputo-cidade-plataforma-estrategica-de-turismo-negocios-e-investimento-sustentavel Publicado; identidade e conteúdo consultáveis na homologação Não foi feita aceitação visual ou conferência pela Tempo nesta passagem. 2026-10-22T21:05:03.369388+00:00
20 171994 Mercado Financeiro tempo Oportunidades 2026-04-06T09:27:00+00:00 2026-04-17T09:53:42+00:00 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 https://investemoz.tempo.co.mz/mercado-financeiro 23 vazio — dependência concreta RecursoQA23; original/hash/ClamD/replay demonstrados. Corpo vazio na origemHTML/Inertia200;privado,sem página publicada. mercado_financeiro {"status": "capturada mas não publicada no ativo", "sha256": "417b4a33468f360acef272ce3e4180c1d0a171980befa73ddb9a33de5a9d4c4e"} Corpo do artigo vazio; imagem não substitui conteúdo. Manter pendente e separado, sem bloquear outros lotes. outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado Original vazio Fornecer corpo editorial pela Tempo; imagem e Fintech não substituem o original. Sem novo endereço público.
21 173480 Modelo de Contrato de Trabalho Doméstico para Pequenas Famílias tempo Trabalho Doméstico 2026-09-15T12:34:17+00:00 2026-09-15T12:34:19+00:00 5b95ad9010fcbdc1000052f47766ca54409d3e0649211729ced9ef142f5c6db0 https://investemoz.tempo.co.mz/modelo-de-contrato-de-trabalho-domestico-para-pequenas-familias 66 existente por integrar inventariado; cadastro/versão por reconciliar 55 trabalho_domestico {"status": "imagem já publicada; reutilizar", "resource_id": 24, "sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"} Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites. Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos. legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas https://teste-investidor.tempo.co.mz/management/portal-services/resources/55/article/preview Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. trabalho_domestico
22 172219 Niassa - O último grande destino virgem do turismo africano tempo Turismo 2026-04-14T10:17:14+00:00 2026-04-14T10:43:56+00:00 47d82a59a86c3cbdef04ea065e711e7139d50137e440f03efbd710e3c4220ef7 https://investemoz.tempo.co.mz/niassa-o-ultimo-grande-destino-virgem-do-turismo-africano/ | https://investemoz.tempo.co.mz/niassa-o-ultimo-grande-destino-virgem-do-turismo-africano 21 publicado RecursoQA21; original/hash/ClamD/replay demonstrados. DivulgaçãoQAseparada;consumidorHTTP/desktop observado;não é oportunidade nem geometria. 26 publicados {"scope": "homologação", "href": "/documentos/27/view", "status": "referência na ficha; binário não reensaiado"} Sem pendência de integração desta identidade; mantém-se a revisão editorial corrente. Preservar a publicação; gerir edições e versões pela biblioteca existente. outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado https://teste-investidor.tempo.co.mz/niassa-o-ultimo-grande-destino-virgem-do-turismo-africano Publicado; identidade e conteúdo consultáveis na homologação Não foi feita aceitação visual ou conferência pela Tempo nesta passagem. 2026-10-22T21:05:03.369388+00:00
23 173476 Procedimentos para Rescisão do Contrato de Trabalho Doméstico segundo o Regulamento de Moçambique tempo Trabalho Doméstico 2026-09-15T11:39:21+00:00 2026-09-15T11:39:24+00:00 bb634a9021da9d7b46ed105317aa54e4af42a16dab042e0ceb2bdc0d702d1a67 https://investemoz.tempo.co.mz/procedimentos-para-rescisao-do-contrato-de-trabalho-domestico-segundo-o-regulamento-de-mocambique 67 existente por integrar inventariado; cadastro/versão por reconciliar 56 trabalho_domestico {"status": "imagem já publicada; reutilizar", "resource_id": 24, "sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"} Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites. Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos. legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas https://teste-investidor.tempo.co.mz/management/portal-services/resources/56/article/preview Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. trabalho_domestico
24 172263 Província da Zambézia: Onde os Corredores de Moçambique Convergem com a Natureza, a Cultura e o Turismo Sustentável tempo Turismo 2026-04-14T14:21:52+00:00 2026-04-14T14:26:27+00:00 9e7ca4e9542135d5969f5c008ce8e29b037cc06a53875cfa7dc3e7452ec47744 https://investemoz.tempo.co.mz/provincia-da-zambezia-onde-os-corredores-de-mocambique-convergem-com-a-natureza-a-cultura-e-o-turismo-sustentavel 11 publicado RecursoQA11; original/hash/ClamD/replay demonstrados. DivulgaçãoQAseparada;consumidorHTTP/desktop observado;não é oportunidade nem geometria. 3 publicados {"scope": "homologação", "href": "/documentos/27/view", "status": "referência na ficha; binário não reensaiado"} Sem pendência de integração desta identidade; mantém-se a revisão editorial corrente. Preservar a publicação; gerir edições e versões pela biblioteca existente. outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado https://teste-investidor.tempo.co.mz/provincia-da-zambezia-onde-os-corredores-de-mocambique-convergem-com-a-natureza-a-cultura-e-o-turismo-sustentavel Publicado; identidade e conteúdo consultáveis na homologação Não foi feita aceitação visual ou conferência pela Tempo nesta passagem. 2026-10-22T21:05:03.369388+00:00
25 172233 Província de Gaza: Oportunidades mais sólidas e estratégicas de investimento no turismo no sul de Moçambique. tempo Turismo 2026-04-14T10:52:30+00:00 2026-04-17T08:28:56+00:00 2bf2df9bd3b7ab8656209a48f4928b5d8bbf2959e77c292fc4c9987e67ba2fea https://investemoz.tempo.co.mz/provincia-de-gaza-oportunidades-mais-solidas-e-estrategicas-de-investimento-no-turismo-no-sul-de-mocambique/ | https://investemoz.tempo.co.mz/provincia-de-gaza-oportunidades-mais-solidas-e-estrategicas-de-investimento-no-turismo-no-sul-de-mocambique 18 publicado RecursoQA18; original/hash/ClamD/replay demonstrados. DivulgaçãoQAseparada;consumidorHTTP/desktop observado;não é oportunidade nem geometria. 23 publicados {"scope": "homologação", "href": "/documentos/27/view", "status": "referência na ficha; binário não reensaiado"} Sem pendência de integração desta identidade; mantém-se a revisão editorial corrente. Preservar a publicação; gerir edições e versões pela biblioteca existente. outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado https://teste-investidor.tempo.co.mz/provincia-de-gaza-oportunidades-mais-solidas-e-estrategicas-de-investimento-no-turismo-no-sul-de-mocambique Publicado; identidade e conteúdo consultáveis na homologação Não foi feita aceitação visual ou conferência pela Tempo nesta passagem. 2026-10-22T21:05:03.369388+00:00
26 172253 Província de Inhambane: Um Destino Estratégico para Investimento Turístico tempo Turismo 2026-04-14T14:15:12+00:00 2026-04-14T14:25:44+00:00 2d1cc729481ce65a5e50898c0eea4a1eb2418273b03fc749009e2ffaaa72fe71 https://investemoz.tempo.co.mz/provincia-de-inhambane-um-destino-estrategico-para-investimento-turistico/ | https://investemoz.tempo.co.mz/provincia-de-inhambane-um-destino-estrategico-para-investimento-turistico 15 publicado RecursoQA15; original/hash/ClamD/replay demonstrados. DivulgaçãoQAseparada;consumidorHTTP/desktop observado;não é oportunidade nem geometria. 6 publicados {"scope": "homologação", "href": "/documentos/27/view", "status": "referência na ficha; binário não reensaiado"} Sem pendência de integração desta identidade; mantém-se a revisão editorial corrente. Preservar a publicação; gerir edições e versões pela biblioteca existente. outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado https://teste-investidor.tempo.co.mz/provincia-de-inhambane-um-destino-estrategico-para-investimento-turistico Publicado; identidade e conteúdo consultáveis na homologação Não foi feita aceitação visual ou conferência pela Tempo nesta passagem. 2026-10-22T21:05:03.369388+00:00
29 172230 Província de Nampula - Perfil de investimento no setor do turismo tempo Turismo 2026-04-14T10:50:32+00:00 2026-04-14T11:10:23+00:00 d18ccc1e036b3480605462ce7176be75153f6aa988bc9b37d8d395b01e555a7a https://investemoz.tempo.co.mz/provincia-de-nampula-perfil-de-investimento-no-setor-do-turismo/ | https://investemoz.tempo.co.mz/provincia-de-nampula-perfil-de-investimento-no-setor-do-turismo 19 publicado RecursoQA19; original/hash/ClamD/replay demonstrados. DivulgaçãoQAseparada;consumidorHTTP/desktop observado;não é oportunidade nem geometria. 24 publicados {"scope": "homologação", "href": "/documentos/27/view", "status": "referência na ficha; binário não reensaiado"} Sem pendência de integração desta identidade; mantém-se a revisão editorial corrente. Preservar a publicação; gerir edições e versões pela biblioteca existente. outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado https://teste-investidor.tempo.co.mz/provincia-de-nampula-perfil-de-investimento-no-setor-do-turismo Publicado; identidade e conteúdo consultáveis na homologação Não foi feita aceitação visual ou conferência pela Tempo nesta passagem. 2026-10-22T21:05:03.369388+00:00
30 172256 Província de Sofala: Onde a Natureza, a História e o Futuro do Turismo Convergem tempo Turismo 2026-04-14T14:16:47+00:00 2026-04-14T14:26:17+00:00 12b46a34ba61c6ef0058a6c410ebc17329d897fe4b06a13b4ea3fe3c85a28342 https://investemoz.tempo.co.mz/provincia-de-sofala-onde-a-natureza-a-historia-e-o-futuro-do-turismo-convergem/ | https://investemoz.tempo.co.mz/provincia-de-sofala-onde-a-natureza-a-historia-e-o-futuro-do-turismo-convergem 12 publicado RecursoQA12; original/hash/ClamD/replay demonstrados. DivulgaçãoQAseparada;consumidorHTTP/desktop observado;não é oportunidade nem geometria. 4 publicados {"scope": "homologação", "href": "/documentos/27/view", "status": "referência na ficha; binário não reensaiado"} Sem pendência de integração desta identidade; mantém-se a revisão editorial corrente. Preservar a publicação; gerir edições e versões pela biblioteca existente. outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado https://teste-investidor.tempo.co.mz/provincia-de-sofala-onde-a-natureza-a-historia-e-o-futuro-do-turismo-convergem Publicado; identidade e conteúdo consultáveis na homologação Não foi feita aceitação visual ou conferência pela Tempo nesta passagem. 2026-10-22T21:05:03.369388+00:00
31 172261 Província de Tete: Onde o Rio Zambeze, a Cultura Viva e a Conservação Comunitária Criam o Futuro do Turismo tempo Turismo 2026-04-14T14:19:48+00:00 2026-04-14T14:26:25+00:00 e78fce2931260a7d6f82270897e906feb640f65d084c2d4ffb5ba19303858275 https://investemoz.tempo.co.mz/provincia-de-tete-onde-o-rio-zambeze-a-cultura-viva-e-a-conservacao-comunitaria-criam-o-futuro-do-turismo/ | https://investemoz.tempo.co.mz/provincia-de-tete-onde-o-rio-zambeze-a-cultura-viva-e-a-conservacao-comunitaria-criam-o-futuro-do-turismo 13 publicado RecursoQA13; original/hash/ClamD/replay demonstrados. DivulgaçãoQAseparada;consumidorHTTP/desktop observado;não é oportunidade nem geometria. 5 publicados {"scope": "homologação", "href": "/documentos/27/view", "status": "referência na ficha; binário não reensaiado"} Sem pendência de integração desta identidade; mantém-se a revisão editorial corrente. Preservar a publicação; gerir edições e versões pela biblioteca existente. outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado https://teste-investidor.tempo.co.mz/provincia-de-tete-onde-o-rio-zambeze-a-cultura-viva-e-a-conservacao-comunitaria-criam-o-futuro-do-turismo Publicado; identidade e conteúdo consultáveis na homologação Não foi feita aceitação visual ou conferência pela Tempo nesta passagem. 2026-10-22T21:05:03.369388+00:00
32 73246 Rofino Licuco preso cinco meses depois de condenado por agressão tempo Nas Redes 2017-07-28T20:23:11+00:00 2017-07-28T20:23:11+00:00 806c54948641f4f5933a2ea4a6935dfb7a67202ab227960451d62435880289f6 https://investemoz.tempo.co.mz/rofino-licuco-preso-cinco-meses-depois-de-condenado-por-agressao 47 existente por integrar inventariado; cadastro/versão por reconciliar 38 historico_noticioso {"status": "imagem já publicada; reutilizar", "resource_id": 24, "sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"} Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites. Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos. legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas https://teste-investidor.tempo.co.mz/management/portal-services/resources/38/article/preview Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. historico_noticioso
33 77232 Sete indivíduos mortos a tiro na Matola tempo Nas Redes 2017-08-11T20:30:32+00:00 2017-08-11T20:30:32+00:00 e657a97d5d6db7fa0ea6b7d14029ff873ea5d36dfdadda5f8b9bc8f1ef5dd011 https://investemoz.tempo.co.mz/sete-individuos-mortos-a-tiro-na-matola 48 existente por integrar inventariado; cadastro/versão por reconciliar 39 historico_noticioso {"status": "imagem já publicada; reutilizar", "resource_id": 24, "sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"} Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites. Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos. legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas https://teste-investidor.tempo.co.mz/management/portal-services/resources/39/article/preview Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. historico_noticioso
34 62416 Sete pessoas morrem e 150 são resgatados em acidente com eléctrico na Índia tempo Categoria 2017-06-26T12:14:48+00:00 2017-06-26T12:14:48+00:00 0fe20a9ca9dd99c58072f43b3dece1de347ef4063ee5f75bcffe8b2f4bf253b5 https://investemoz.tempo.co.mz/sete-pessoas-morrem-e-150-sao-resgatados-em-acidente-com-electrico-na-india 55 existente por integrar inventariado; cadastro/versão por reconciliar 40 historico_noticioso {"status": "imagem já publicada; reutilizar", "resource_id": 24, "sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"} Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites. Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos. legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas https://teste-investidor.tempo.co.mz/management/portal-services/resources/40/article/preview Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. historico_noticioso
35 173209 Fintech tempo Oportunidades 2026-06-16T11:24:22+00:00 2026-06-23T10:03:06+00:00 8db9206bf5192b68e7c9bdfb87e9dc9c7063a4b7eca56630685b0e6c294f55ab https://investemoz.tempo.co.mz/tech-fintech 27 publicado inventariado; cadastro/versão por reconciliar 28 publicados {"scope": "homologação", "href": "/documentos/27/view", "status": "referência na ficha; binário não reensaiado"} Sem pendência de integração desta identidade; mantém-se a revisão editorial corrente. Preservar a publicação; gerir edições e versões pela biblioteca existente. outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado https://teste-investidor.tempo.co.mz/tech-fintech Publicado; identidade e conteúdo consultáveis na homologação Não foi feita aceitação visual ou conferência pela Tempo nesta passagem. 2026-10-22T21:05:03.369388+00:00
36 171990 Turismo tempo Oportunidades 2026-04-06T09:22:51+00:00 2026-06-16T13:07:59+00:00 604d30a740cc6ed20d24e3447806e733aa4aefb9f339f81d3dc57b644dd19fb7 https://investemoz.tempo.co.mz/turismo 10 publicado RecursoQA10; original/hash/ClamD/replay demonstrados. DivulgaçãoQAseparada;consumidorHTTP/desktop observado;não é oportunidade nem geometria. 19 publicados {"scope": "homologação", "href": "/documentos/15/view", "status": "referência na ficha; binário não reensaiado"} Sem pendência de integração desta identidade; mantém-se a revisão editorial corrente. Preservar a publicação; gerir edições e versões pela biblioteca existente. outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado https://teste-investidor.tempo.co.mz/turismo Publicado; identidade e conteúdo consultáveis na homologação Não foi feita aceitação visual ou conferência pela Tempo nesta passagem. 2026-10-22T21:05:03.369388+00:00
37 74364 tempo Categoria 2017-07-26T21:20:33+00:00 2017-07-26T21:20:33+00:00 2a7f0a5b09e0136fec9eae357ac8a88a159ead23b25ed7bb735c75997b10ec5e https://investemoz.tempo.co.mz/74364-2 dependência concreta — título ausente inventariado; cadastro/versão por reconciliar identidade_incompleta {"status": "imagem já publicada; reutilizar", "resource_id": 24, "sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"} Referência não recolhida: http://www.newsaiep.com/moz_news/wp-content/uploads/1501695692_133_notitle.jpg | Referência não recolhida: https://www.facebook.com/179463555444013/posts/1552815684775453 | Título vazio no post 74364; corpo com 20 caracteres; identidade editorial a esclarecer sem inventar título. Preservar exemplar e referências; delimitar recuperação editorial antes de integrar. outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado Original sem título Identificação editorial pela Tempo para o post 74364; corpo com 21 caracteres úteis preservado. Sem título ou endereço novo inventado.
38 62929 Daúde Razaque já não é treinador da Liga Desportiva de Maputo tempo Nas Redes 2017-06-27T16:41:43+00:00 2017-06-27T16:41:43+00:00 c3b57ea685290bfb01c50403bc83db98cf1a42ad51c04d5b8d288c99b8c0a808 https://investemoz.tempo.co.mz/daude-razaque-ja-nao-e-treinador-da-liga-desportiva-de-maputo 49 existente por integrar inventariado; cadastro/versão por reconciliar 41 historico_noticioso {"status": "imagem já publicada; reutilizar", "resource_id": 24, "sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"} Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites. Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos. legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas https://teste-investidor.tempo.co.mz/management/portal-services/resources/41/article/preview Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. historico_noticioso
39 62649 Daviz Simango não se incomoda com possível sucessão na Beira tempo Nas Redes 2017-06-26T19:38:09+00:00 2017-06-26T19:38:09+00:00 090e5f41ed11c8e680d2e30ea5c8310b7c6b165ddec318a8b0cf94927f78da74 https://investemoz.tempo.co.mz/daviz-simango-nao-se-incomoda-com-possivel-sucessao-na-beira 56 existente por integrar inventariado; cadastro/versão por reconciliar 42 historico_noticioso {"status": "imagem já publicada; reutilizar", "resource_id": 24, "sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"} Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites. Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos. legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas https://teste-investidor.tempo.co.mz/management/portal-services/resources/42/article/preview Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. historico_noticioso
40 173470 Direitos e Deveres do Trabalhador Doméstico tempo Trabalho Doméstico 2026-09-15T11:28:25+00:00 2026-09-15T11:28:28+00:00 f69fd6fd8509913f2d79df0267c469dc7a9d0d30420ce8428749d53ba21446ab https://investemoz.tempo.co.mz/direitos-e-deveres-do-trabalhador-domestico 68 existente por integrar inventariado; cadastro/versão por reconciliar 57 trabalho_domestico {"status": "imagem já publicada; reutilizar", "resource_id": 24, "sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"} Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites. Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos. legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas https://teste-investidor.tempo.co.mz/management/portal-services/resources/57/article/preview Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. trabalho_domestico
41 172679 Energia tempo Featured 2026-04-30T13:03:57+00:00 2026-04-30T13:28:48+00:00 e2a7eb4be0a2cf4139737f6428c147ac35de99899478ce79195dc5e1b0a960f3 https://investemoz.tempo.co.mz/energia | https://investemoz.tempo.co.mz/energia/ 8 publicado RecursoQA8; original/hash/ClamD/replay demonstrados. DivulgaçãoQAseparada;consumidorHTTP/desktop observado;não é oportunidade nem geometria. 1 publicados {"scope": "homologação", "href": "/documentos/27/view", "status": "referência na ficha; binário não reensaiado"} Sem pendência de integração desta identidade; mantém-se a revisão editorial corrente. Preservar a publicação; gerir edições e versões pela biblioteca existente. outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado https://teste-investidor.tempo.co.mz/energia Publicado; identidade e conteúdo consultáveis na homologação Não foi feita aceitação visual ou conferência pela Tempo nesta passagem. 2026-10-22T21:05:03.369388+00:00
42 62739 “Exigimos responsabilização dos agentes do crime”, Daviz Simango tempo Nas Redes 2017-06-26T23:13:36+00:00 2017-06-26T23:13:36+00:00 e37ea18176246dab529d2ecec7734f0fb2b0a54cadc507fbd789033d5b8e5b93 https://investemoz.tempo.co.mz/exigimos-responsabilizacao-dos-agentes-do-crime-daviz-simango-2 57 existente por integrar inventariado; cadastro/versão por reconciliar 43 historico_noticioso {"status": "imagem já publicada; reutilizar", "resource_id": 24, "sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"} Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites. Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos. legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas https://teste-investidor.tempo.co.mz/management/portal-services/resources/43/article/preview Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. historico_noticioso
43 62854 Governo e parceiros reforçam mecanismos de controlo de HIV/Sida tempo Nas Redes 2017-06-27T11:07:53+00:00 2017-06-27T11:07:53+00:00 0073816b1fa4bd50ec6417b07272da06565230d06a19079ca76cb006c882d2f1 https://investemoz.tempo.co.mz/governo-e-parceiros-reforcam-mecanismos-de-controlo-de-hivsida 58 existente por integrar inventariado; cadastro/versão por reconciliar 44 historico_noticioso {"status": "imagem já publicada; reutilizar", "resource_id": 24, "sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"} Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites. Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos. legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas https://teste-investidor.tempo.co.mz/management/portal-services/resources/44/article/preview Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. historico_noticioso
44 63128 Graça Machel diz que desnutrição é um problema de desenvolvimento tempo Nas Redes 2017-06-27T22:01:59+00:00 2017-06-27T22:01:59+00:00 711f3707e3a0d72ec919216fc50823de9060349104ee98fe0a6b20d6684b6031 https://investemoz.tempo.co.mz/graca-machel-diz-que-desnutricao-e-um-problema-de-desenvolvimento 59 existente por integrar inventariado; cadastro/versão por reconciliar 45 historico_noticioso {"status": "imagem já publicada; reutilizar", "resource_id": 24, "sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"} Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites. Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos. legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas https://teste-investidor.tempo.co.mz/management/portal-services/resources/45/article/preview Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. historico_noticioso
45 63181 In dependência tempo Nas Redes 2017-06-27T21:38:06+00:00 2017-06-27T21:38:06+00:00 b0d87c0d58d6d1343d102cd3141bda1215c5afd54f3780a1c6c262d786dbfe27 https://investemoz.tempo.co.mz/in-dependencia 60 existente por integrar inventariado; cadastro/versão por reconciliar 46 historico_noticioso {"status": "imagem já publicada; reutilizar", "resource_id": 24, "sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"} Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites. Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos. legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas https://teste-investidor.tempo.co.mz/management/portal-services/resources/46/article/preview Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. historico_noticioso
46 63146 Kroll não descobriu como foram gastos 2 biliões de dólares; Governo de Nyusi não apoiou a Auditoria; criação da Proindicus, EMATUM e MAM não foi para proteção da Zona Económica Exclusiva tempo Nas Redes 2017-06-27T19:30:01+00:00 2017-06-27T19:30:01+00:00 4ccbad5c92ddc3884f58181a4f1ed3c1d3d6a59f005bfd99b197a6fc1fc3c280 https://investemoz.tempo.co.mz/kroll-nao-descobriu-como-foram-gastos-2-bilioes-de-dolares-governo-de-nyusi-nao-apoiou-a-auditoria-criacao-da-proindicus-ematum-e-mam-nao-foi-para-protecao-da-zona-economica-exclusiva-2 61 existente por integrar inventariado; cadastro/versão por reconciliar 47 historico_noticioso {"status": "imagem já publicada; reutilizar", "resource_id": 24, "sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"} Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites. Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos. legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas https://teste-investidor.tempo.co.mz/management/portal-services/resources/47/article/preview Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. historico_noticioso
47 172674 Mineração tempo Featured 2026-04-30T13:12:09+00:00 2026-04-30T13:30:13+00:00 8d3acc292ed806bc4cc7bcb6a3a6a5759f30b610766a5ef4b3d6a0b7c56d5861 https://investemoz.tempo.co.mz/mineracao 9 publicado RecursoQA9; original/hash/ClamD/replay demonstrados. DivulgaçãoQAseparada;consumidorHTTP/desktop observado;não é oportunidade nem geometria. 2 publicados {"scope": "homologação", "href": "/documentos/27/view", "status": "referência na ficha; binário não reensaiado"} Sem pendência de integração desta identidade; mantém-se a revisão editorial corrente. Preservar a publicação; gerir edições e versões pela biblioteca existente. outputs/legacy-correspondence-20261022/public-pages.json; qa-import-receipt.json; editorial-gaps-current.json; inventário histórico de 15/10 preservado https://teste-investidor.tempo.co.mz/mineracao Publicado; identidade e conteúdo consultáveis na homologação Não foi feita aceitação visual ou conferência pela Tempo nesta passagem. 2026-10-22T21:05:03.369388+00:00
48 62688 O Instituto Nacional de Meteorologia prevê o seguinte estado do tempo para esta ... tempo Nas Redes 2017-06-26T18:21:18+00:00 2017-06-26T18:21:18+00:00 396fe2575641622015505eb11d4c7152b6e3e1b741966f14c0cd1673fd455fd3 https://investemoz.tempo.co.mz/o-instituto-nacional-de-meteorologia-preve-o-seguinte-estado-do-tempo-para-esta-29 62 existente por integrar inventariado; cadastro/versão por reconciliar 48 historico_noticioso {"status": "imagem já publicada; reutilizar", "resource_id": 24, "sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"} Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites. Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos. legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas https://teste-investidor.tempo.co.mz/management/portal-services/resources/48/article/preview Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. historico_noticioso
49 173474 Sanções Disciplinares Aplicáveis a Trabalhadores Domésticos segundo o Regulamento de Moçambique tempo Trabalho Doméstico 2026-09-15T11:37:04+00:00 2026-09-15T11:37:06+00:00 e09a0fe71397e2b060ff8435111117aa487b476a85ec522fffcdf9e23a6cdac2 https://investemoz.tempo.co.mz/sancoes-disciplinares-aplicaveis-a-trabalhadores-domesticos-segundo-o-regulamento-de-mocambique 69 existente por integrar inventariado; cadastro/versão por reconciliar 58 trabalho_domestico {"status": "imagem já publicada; reutilizar", "resource_id": 24, "sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"} Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites. Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos. legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas https://teste-investidor.tempo.co.mz/management/portal-services/resources/58/article/preview Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. trabalho_domestico
50 63248 Dois alunos da 9ª classe caíram nas mãos da Polícia da República de Moçambique (... tempo Nas Redes 2017-06-28T11:30:01+00:00 2017-06-28T11:30:01+00:00 2220ca2c07d10f10b2bd6fd0f103ee733b83570d436c751c449c85b42df46a13 https://investemoz.tempo.co.mz/dois-alunos-da-9a-classe-cairam-nas-maos-da-policia-da-republica-de-mocambique 50 existente por integrar inventariado; cadastro/versão por reconciliar 49 historico_noticioso {"status": "imagem já publicada; reutilizar", "resource_id": 24, "sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"} Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites. Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos. legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas https://teste-investidor.tempo.co.mz/management/portal-services/resources/49/article/preview Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. historico_noticioso
51 173463 Regulamento do Trabalho Doméstico tempo Trabalho Doméstico 2026-09-15T10:56:07+00:00 2026-09-15T12:41:44+00:00 eb43b6c1f097631250f0b36d3d01b4d106db2fcae07a56bb154819143fff11e9 https://investemoz.tempo.co.mz/regulamento-do-trabalho-domestico 70 existente por integrar inventariado; cadastro/versão por reconciliar 59 trabalho_domestico {"status": "imagem já publicada; reutilizar", "resource_id": 24, "sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"} Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites. Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos. legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas https://teste-investidor.tempo.co.mz/management/portal-services/resources/59/article/preview Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. trabalho_domestico
@@ -2,32 +2,32 @@
Conferência HTTP individual em 2026-10-22T21:05:03.369388+00:00. Lista por identidade editorial; imagens, recursos da biblioteca e variantes de URL não são artigos adicionais. Não altera publicações. Endereço em branco significa que não foi demonstrada uma página nova, não uma ligação a criar por suposição.
Actualização editorial QA: 28 com editor/leitor/prévia; pacote privado preparado, não transportado. Os 20 públicos conservam a conferência anterior, sem novas consultas. Guia e recibo: `legado-editorial-qa-20261022.json`.
Actualização de homologação, 22/10/2026: os 28 estão cadastrados privadamente, com pesquisa/editor/prévia/recuperação conferidos. Os seus endereços abaixo são privados e exigem conta autorizada; não são artigos publicados. Os 20 públicos conservam a conferência anterior, sem novas consultas. Recibo: `../../unification/legacy-editorial-installation-receipt-20261022.json`.
| ID | Título original | Endereço original | Endereço no novo portal | Estado | Pendência |
| --- | --- | --- | --- | --- | --- |
| 171985 | Agronegocio | https://investemoz.tempo.co.mz/agronegocio | https://teste-investidor.tempo.co.mz/agronegocio | Publicado; identidade e conteúdo consultáveis na homologação | Não foi feita aceitação visual ou conferência pela Tempo nesta passagem. |
| 62413 | 15/30 costa do sol com dua na zona de mupulene muito lindo | https://investemoz.tempo.co.mz/1530-costa-do-sol-com-dua-na-zona-de-mupulene-muito-lindo | | Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada | Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. Rever/corrigir a nota de demonstração QA antes de publicar. |
| 173220 | Alumínio “Verde” | https://investemoz.tempo.co.mz/aluminio-verde | | Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada | Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. |
| 73945 | Banco Mundial classifica de proibitivos juros praticados em Moçambique | https://investemoz.tempo.co.mz/banco-mundial-classifica-de-proibitivos-juros-praticados-em-mocambique | | Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada | Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. |
| 62413 | 15/30 costa do sol com dua na zona de mupulene muito lindo | https://investemoz.tempo.co.mz/1530-costa-do-sol-com-dua-na-zona-de-mupulene-muito-lindo | https://teste-investidor.tempo.co.mz/management/portal-services/resources/32/article/preview | Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos | Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. Rever/corrigir a nota de demonstração QA antes de publicar. |
| 173220 | Alumínio “Verde” | https://investemoz.tempo.co.mz/aluminio-verde | https://teste-investidor.tempo.co.mz/management/portal-services/resources/50/article/preview | Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos | Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. |
| 73945 | Banco Mundial classifica de proibitivos juros praticados em Moçambique | https://investemoz.tempo.co.mz/banco-mundial-classifica-de-proibitivos-juros-praticados-em-mocambique | https://teste-investidor.tempo.co.mz/management/portal-services/resources/33/article/preview | Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos | Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. |
| 173213 | Bens de Consumo e Franquias | https://investemoz.tempo.co.mz/bens-de-consumo-franquias | https://teste-investidor.tempo.co.mz/bens-de-consumo-franquias | Publicado; identidade e conteúdo consultáveis na homologação | Não foi feita aceitação visual ou conferência pela Tempo nesta passagem. |
| 172223 | Cabo Delgado - Pemba, A capital do turismo costeiro do norte de Moçambique | https://investemoz.tempo.co.mz/cabo-delgado-pemba-a-capital-do-turismo-costeiro-do-norte-de-mocambique/ \| https://investemoz.tempo.co.mz/cabo-delgado-pemba-a-capital-do-turismo-costeiro-do-norte-de-mocambique | https://teste-investidor.tempo.co.mz/cabo-delgado-pemba-a-capital-do-turismo-costeiro-do-norte-de-mocambique | Publicado; identidade e conteúdo consultáveis na homologação | Não foi feita aceitação visual ou conferência pela Tempo nesta passagem. |
| 173482 | Checklist para Contratação e Direitos do Trabalho Doméstico | https://investemoz.tempo.co.mz/checklist-para-contratacao-e-direitos-do-trabalho-domestico | | Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada | Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. |
| 76474 | Coloque o seu número para o grupo de whatsapp, venda de acessórios de computador | https://investemoz.tempo.co.mz/coloque-o-seu-numero-para-o-grupo-de-whatsapp-venda-de-acessorios-de-computador | | Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada | Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. |
| 173484 | Direitos Especiais da Trabalhadora Doméstica no Regulamento | https://investemoz.tempo.co.mz/direitos-especiais-da-trabalhadora-domestica-no-regulamento | | Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada | Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. |
| 173482 | Checklist para Contratação e Direitos do Trabalho Doméstico | https://investemoz.tempo.co.mz/checklist-para-contratacao-e-direitos-do-trabalho-domestico | https://teste-investidor.tempo.co.mz/management/portal-services/resources/52/article/preview | Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos | Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. |
| 76474 | Coloque o seu número para o grupo de whatsapp, venda de acessórios de computador | https://investemoz.tempo.co.mz/coloque-o-seu-numero-para-o-grupo-de-whatsapp-venda-de-acessorios-de-computador | https://teste-investidor.tempo.co.mz/management/portal-services/resources/34/article/preview | Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos | Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. |
| 173484 | Direitos Especiais da Trabalhadora Doméstica no Regulamento | https://investemoz.tempo.co.mz/direitos-especiais-da-trabalhadora-domestica-no-regulamento | https://teste-investidor.tempo.co.mz/management/portal-services/resources/53/article/preview | Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos | Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. |
| 171981 | Energia & Mineração | https://investemoz.tempo.co.mz/energia-mineracao | https://teste-investidor.tempo.co.mz/energia-mineracao | Publicado; identidade e conteúdo consultáveis na homologação | Não foi feita aceitação visual ou conferência pela Tempo nesta passagem. |
| 173222 | Energia e Recursos Naturais | https://investemoz.tempo.co.mz/energia-recursos-naturais | | Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada | Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. |
| 62414 | Frelimo e Zanu-PF: Evocados laços de irmandade | https://investemoz.tempo.co.mz/frelimo-e-zanu-pf-evocados-lacos-de-irmandade | | Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada | Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. |
| 62418 | Governo dará todo O apoio à PGR no seguimento da auditoria | https://investemoz.tempo.co.mz/governo-dara-todo-o-apoio-a-pgr-no-seguimento-da-auditoria | | Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada | Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. |
| 75002 | Governo pretende construir 100 mil habitações em dois anos | https://investemoz.tempo.co.mz/governo-pretende-construir-100-mil-habitacoes-em-dois-anos | | Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada | Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. |
| 173478 | Guia Prático para Empregadores sobre Contratação e Direitos no Trabalho Doméstico | https://investemoz.tempo.co.mz/guia-pratico-para-empregadores-sobre-contratacao-e-direitos-no-trabalho-domestico | | Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada | Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. |
| 173222 | Energia e Recursos Naturais | https://investemoz.tempo.co.mz/energia-recursos-naturais | https://teste-investidor.tempo.co.mz/management/portal-services/resources/51/article/preview | Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos | Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. |
| 62414 | Frelimo e Zanu-PF: Evocados laços de irmandade | https://investemoz.tempo.co.mz/frelimo-e-zanu-pf-evocados-lacos-de-irmandade | https://teste-investidor.tempo.co.mz/management/portal-services/resources/35/article/preview | Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos | Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. |
| 62418 | Governo dará todo O apoio à PGR no seguimento da auditoria | https://investemoz.tempo.co.mz/governo-dara-todo-o-apoio-a-pgr-no-seguimento-da-auditoria | https://teste-investidor.tempo.co.mz/management/portal-services/resources/36/article/preview | Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos | Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. |
| 75002 | Governo pretende construir 100 mil habitações em dois anos | https://investemoz.tempo.co.mz/governo-pretende-construir-100-mil-habitacoes-em-dois-anos | https://teste-investidor.tempo.co.mz/management/portal-services/resources/37/article/preview | Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos | Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. |
| 173478 | Guia Prático para Empregadores sobre Contratação e Direitos no Trabalho Doméstico | https://investemoz.tempo.co.mz/guia-pratico-para-empregadores-sobre-contratacao-e-direitos-no-trabalho-domestico | https://teste-investidor.tempo.co.mz/management/portal-services/resources/54/article/preview | Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos | Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. |
| 173218 | Imobiliário | https://investemoz.tempo.co.mz/imobiliario | https://teste-investidor.tempo.co.mz/imobiliario | Publicado; identidade e conteúdo consultáveis na homologação | Não foi feita aceitação visual ou conferência pela Tempo nesta passagem. |
| 171992 | Infraestruturas | https://investemoz.tempo.co.mz/infraestruturas | https://teste-investidor.tempo.co.mz/infraestruturas | Publicado; identidade e conteúdo consultáveis na homologação | Não foi feita aceitação visual ou conferência pela Tempo nesta passagem. |
| 172240 | Maputo Cidade – Plataforma Estratégica de Turismo, Negócios e Investimento Sustentável | https://investemoz.tempo.co.mz/maputo-cidade-plataforma-estrategica-de-turismo-negocios-e-investimento-sustentavel/ \| https://investemoz.tempo.co.mz/maputo-cidade-plataforma-estrategica-de-turismo-negocios-e-investimento-sustentavel | https://teste-investidor.tempo.co.mz/maputo-cidade-plataforma-estrategica-de-turismo-negocios-e-investimento-sustentavel | Publicado; identidade e conteúdo consultáveis na homologação | Não foi feita aceitação visual ou conferência pela Tempo nesta passagem. |
| 171994 | Mercado Financeiro | https://investemoz.tempo.co.mz/mercado-financeiro | | Original vazio | Fornecer corpo editorial pela Tempo; imagem e Fintech não substituem o original. Sem novo endereço público. |
| 173480 | Modelo de Contrato de Trabalho Doméstico para Pequenas Famílias | https://investemoz.tempo.co.mz/modelo-de-contrato-de-trabalho-domestico-para-pequenas-familias | | Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada | Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. |
| 173480 | Modelo de Contrato de Trabalho Doméstico para Pequenas Famílias | https://investemoz.tempo.co.mz/modelo-de-contrato-de-trabalho-domestico-para-pequenas-familias | https://teste-investidor.tempo.co.mz/management/portal-services/resources/55/article/preview | Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos | Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. |
| 172219 | Niassa - O último grande destino virgem do turismo africano | https://investemoz.tempo.co.mz/niassa-o-ultimo-grande-destino-virgem-do-turismo-africano/ \| https://investemoz.tempo.co.mz/niassa-o-ultimo-grande-destino-virgem-do-turismo-africano | https://teste-investidor.tempo.co.mz/niassa-o-ultimo-grande-destino-virgem-do-turismo-africano | Publicado; identidade e conteúdo consultáveis na homologação | Não foi feita aceitação visual ou conferência pela Tempo nesta passagem. |
| 173476 | Procedimentos para Rescisão do Contrato de Trabalho Doméstico segundo o Regulamento de Moçambique | https://investemoz.tempo.co.mz/procedimentos-para-rescisao-do-contrato-de-trabalho-domestico-segundo-o-regulamento-de-mocambique | | Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada | Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. |
| 173476 | Procedimentos para Rescisão do Contrato de Trabalho Doméstico segundo o Regulamento de Moçambique | https://investemoz.tempo.co.mz/procedimentos-para-rescisao-do-contrato-de-trabalho-domestico-segundo-o-regulamento-de-mocambique | https://teste-investidor.tempo.co.mz/management/portal-services/resources/56/article/preview | Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos | Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. |
| 172263 | Província da Zambézia: Onde os Corredores de Moçambique Convergem com a Natureza, a Cultura e o Turismo Sustentável | https://investemoz.tempo.co.mz/provincia-da-zambezia-onde-os-corredores-de-mocambique-convergem-com-a-natureza-a-cultura-e-o-turismo-sustentavel | https://teste-investidor.tempo.co.mz/provincia-da-zambezia-onde-os-corredores-de-mocambique-convergem-com-a-natureza-a-cultura-e-o-turismo-sustentavel | Publicado; identidade e conteúdo consultáveis na homologação | Não foi feita aceitação visual ou conferência pela Tempo nesta passagem. |
| 172233 | Província de Gaza: Oportunidades mais sólidas e estratégicas de investimento no turismo no sul de Moçambique. | https://investemoz.tempo.co.mz/provincia-de-gaza-oportunidades-mais-solidas-e-estrategicas-de-investimento-no-turismo-no-sul-de-mocambique/ \| https://investemoz.tempo.co.mz/provincia-de-gaza-oportunidades-mais-solidas-e-estrategicas-de-investimento-no-turismo-no-sul-de-mocambique | https://teste-investidor.tempo.co.mz/provincia-de-gaza-oportunidades-mais-solidas-e-estrategicas-de-investimento-no-turismo-no-sul-de-mocambique | Publicado; identidade e conteúdo consultáveis na homologação | Não foi feita aceitação visual ou conferência pela Tempo nesta passagem. |
| 172253 | Província de Inhambane: Um Destino Estratégico para Investimento Turístico | https://investemoz.tempo.co.mz/provincia-de-inhambane-um-destino-estrategico-para-investimento-turistico/ \| https://investemoz.tempo.co.mz/provincia-de-inhambane-um-destino-estrategico-para-investimento-turistico | https://teste-investidor.tempo.co.mz/provincia-de-inhambane-um-destino-estrategico-para-investimento-turistico | Publicado; identidade e conteúdo consultáveis na homologação | Não foi feita aceitação visual ou conferência pela Tempo nesta passagem. |
@@ -36,23 +36,23 @@ Actualização editorial QA: 28 com editor/leitor/prévia; pacote privado prepar
| 172230 | Província de Nampula - Perfil de investimento no setor do turismo | https://investemoz.tempo.co.mz/provincia-de-nampula-perfil-de-investimento-no-setor-do-turismo/ \| https://investemoz.tempo.co.mz/provincia-de-nampula-perfil-de-investimento-no-setor-do-turismo | https://teste-investidor.tempo.co.mz/provincia-de-nampula-perfil-de-investimento-no-setor-do-turismo | Publicado; identidade e conteúdo consultáveis na homologação | Não foi feita aceitação visual ou conferência pela Tempo nesta passagem. |
| 172256 | Província de Sofala: Onde a Natureza, a História e o Futuro do Turismo Convergem | https://investemoz.tempo.co.mz/provincia-de-sofala-onde-a-natureza-a-historia-e-o-futuro-do-turismo-convergem/ \| https://investemoz.tempo.co.mz/provincia-de-sofala-onde-a-natureza-a-historia-e-o-futuro-do-turismo-convergem | https://teste-investidor.tempo.co.mz/provincia-de-sofala-onde-a-natureza-a-historia-e-o-futuro-do-turismo-convergem | Publicado; identidade e conteúdo consultáveis na homologação | Não foi feita aceitação visual ou conferência pela Tempo nesta passagem. |
| 172261 | Província de Tete: Onde o Rio Zambeze, a Cultura Viva e a Conservação Comunitária Criam o Futuro do Turismo | https://investemoz.tempo.co.mz/provincia-de-tete-onde-o-rio-zambeze-a-cultura-viva-e-a-conservacao-comunitaria-criam-o-futuro-do-turismo/ \| https://investemoz.tempo.co.mz/provincia-de-tete-onde-o-rio-zambeze-a-cultura-viva-e-a-conservacao-comunitaria-criam-o-futuro-do-turismo | https://teste-investidor.tempo.co.mz/provincia-de-tete-onde-o-rio-zambeze-a-cultura-viva-e-a-conservacao-comunitaria-criam-o-futuro-do-turismo | Publicado; identidade e conteúdo consultáveis na homologação | Não foi feita aceitação visual ou conferência pela Tempo nesta passagem. |
| 73246 | Rofino Licuco preso cinco meses depois de condenado por agressão | https://investemoz.tempo.co.mz/rofino-licuco-preso-cinco-meses-depois-de-condenado-por-agressao | | Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada | Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. |
| 77232 | Sete indivíduos mortos a tiro na Matola | https://investemoz.tempo.co.mz/sete-individuos-mortos-a-tiro-na-matola | | Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada | Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. |
| 62416 | Sete pessoas morrem e 150 são resgatados em acidente com eléctrico na Índia | https://investemoz.tempo.co.mz/sete-pessoas-morrem-e-150-sao-resgatados-em-acidente-com-electrico-na-india | | Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada | Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. |
| 73246 | Rofino Licuco preso cinco meses depois de condenado por agressão | https://investemoz.tempo.co.mz/rofino-licuco-preso-cinco-meses-depois-de-condenado-por-agressao | https://teste-investidor.tempo.co.mz/management/portal-services/resources/38/article/preview | Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos | Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. |
| 77232 | Sete indivíduos mortos a tiro na Matola | https://investemoz.tempo.co.mz/sete-individuos-mortos-a-tiro-na-matola | https://teste-investidor.tempo.co.mz/management/portal-services/resources/39/article/preview | Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos | Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. |
| 62416 | Sete pessoas morrem e 150 são resgatados em acidente com eléctrico na Índia | https://investemoz.tempo.co.mz/sete-pessoas-morrem-e-150-sao-resgatados-em-acidente-com-electrico-na-india | https://teste-investidor.tempo.co.mz/management/portal-services/resources/40/article/preview | Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos | Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. |
| 173209 | Fintech | https://investemoz.tempo.co.mz/tech-fintech | https://teste-investidor.tempo.co.mz/tech-fintech | Publicado; identidade e conteúdo consultáveis na homologação | Não foi feita aceitação visual ou conferência pela Tempo nesta passagem. |
| 171990 | Turismo | https://investemoz.tempo.co.mz/turismo | https://teste-investidor.tempo.co.mz/turismo | Publicado; identidade e conteúdo consultáveis na homologação | Não foi feita aceitação visual ou conferência pela Tempo nesta passagem. |
| 74364 | | https://investemoz.tempo.co.mz/74364-2 | | Original sem título | Identificação editorial pela Tempo para o post 74364; corpo com 21 caracteres úteis preservado. Sem título ou endereço novo inventado. |
| 62929 | Daúde Razaque já não é treinador da Liga Desportiva de Maputo | https://investemoz.tempo.co.mz/daude-razaque-ja-nao-e-treinador-da-liga-desportiva-de-maputo | | Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada | Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. |
| 62649 | Daviz Simango não se incomoda com possível sucessão na Beira | https://investemoz.tempo.co.mz/daviz-simango-nao-se-incomoda-com-possivel-sucessao-na-beira | | Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada | Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. |
| 173470 | Direitos e Deveres do Trabalhador Doméstico | https://investemoz.tempo.co.mz/direitos-e-deveres-do-trabalhador-domestico | | Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada | Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. |
| 62929 | Daúde Razaque já não é treinador da Liga Desportiva de Maputo | https://investemoz.tempo.co.mz/daude-razaque-ja-nao-e-treinador-da-liga-desportiva-de-maputo | https://teste-investidor.tempo.co.mz/management/portal-services/resources/41/article/preview | Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos | Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. |
| 62649 | Daviz Simango não se incomoda com possível sucessão na Beira | https://investemoz.tempo.co.mz/daviz-simango-nao-se-incomoda-com-possivel-sucessao-na-beira | https://teste-investidor.tempo.co.mz/management/portal-services/resources/42/article/preview | Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos | Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. |
| 173470 | Direitos e Deveres do Trabalhador Doméstico | https://investemoz.tempo.co.mz/direitos-e-deveres-do-trabalhador-domestico | https://teste-investidor.tempo.co.mz/management/portal-services/resources/57/article/preview | Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos | Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. |
| 172679 | Energia | https://investemoz.tempo.co.mz/energia \| https://investemoz.tempo.co.mz/energia/ | https://teste-investidor.tempo.co.mz/energia | Publicado; identidade e conteúdo consultáveis na homologação | Não foi feita aceitação visual ou conferência pela Tempo nesta passagem. |
| 62739 | “Exigimos responsabilização dos agentes do crime”, Daviz Simango | https://investemoz.tempo.co.mz/exigimos-responsabilizacao-dos-agentes-do-crime-daviz-simango-2 | | Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada | Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. |
| 62854 | Governo e parceiros reforçam mecanismos de controlo de HIV/Sida | https://investemoz.tempo.co.mz/governo-e-parceiros-reforcam-mecanismos-de-controlo-de-hivsida | | Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada | Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. |
| 63128 | Graça Machel diz que desnutrição é um problema de desenvolvimento | https://investemoz.tempo.co.mz/graca-machel-diz-que-desnutricao-e-um-problema-de-desenvolvimento | | Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada | Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. |
| 63181 | In dependência | https://investemoz.tempo.co.mz/in-dependencia | | Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada | Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. |
| 63146 | Kroll não descobriu como foram gastos 2 biliões de dólares; Governo de Nyusi não apoiou a Auditoria; criação da Proindicus, EMATUM e MAM não foi para proteção da Zona Económica Exclusiva | https://investemoz.tempo.co.mz/kroll-nao-descobriu-como-foram-gastos-2-bilioes-de-dolares-governo-de-nyusi-nao-apoiou-a-auditoria-criacao-da-proindicus-ematum-e-mam-nao-foi-para-protecao-da-zona-economica-exclusiva-2 | | Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada | Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. |
| 62739 | “Exigimos responsabilização dos agentes do crime”, Daviz Simango | https://investemoz.tempo.co.mz/exigimos-responsabilizacao-dos-agentes-do-crime-daviz-simango-2 | https://teste-investidor.tempo.co.mz/management/portal-services/resources/43/article/preview | Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos | Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. |
| 62854 | Governo e parceiros reforçam mecanismos de controlo de HIV/Sida | https://investemoz.tempo.co.mz/governo-e-parceiros-reforcam-mecanismos-de-controlo-de-hivsida | https://teste-investidor.tempo.co.mz/management/portal-services/resources/44/article/preview | Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos | Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. |
| 63128 | Graça Machel diz que desnutrição é um problema de desenvolvimento | https://investemoz.tempo.co.mz/graca-machel-diz-que-desnutricao-e-um-problema-de-desenvolvimento | https://teste-investidor.tempo.co.mz/management/portal-services/resources/45/article/preview | Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos | Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. |
| 63181 | In dependência | https://investemoz.tempo.co.mz/in-dependencia | https://teste-investidor.tempo.co.mz/management/portal-services/resources/46/article/preview | Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos | Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. |
| 63146 | Kroll não descobriu como foram gastos 2 biliões de dólares; Governo de Nyusi não apoiou a Auditoria; criação da Proindicus, EMATUM e MAM não foi para proteção da Zona Económica Exclusiva | https://investemoz.tempo.co.mz/kroll-nao-descobriu-como-foram-gastos-2-bilioes-de-dolares-governo-de-nyusi-nao-apoiou-a-auditoria-criacao-da-proindicus-ematum-e-mam-nao-foi-para-protecao-da-zona-economica-exclusiva-2 | https://teste-investidor.tempo.co.mz/management/portal-services/resources/47/article/preview | Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos | Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. |
| 172674 | Mineração | https://investemoz.tempo.co.mz/mineracao | https://teste-investidor.tempo.co.mz/mineracao | Publicado; identidade e conteúdo consultáveis na homologação | Não foi feita aceitação visual ou conferência pela Tempo nesta passagem. |
| 62688 | O Instituto Nacional de Meteorologia prevê o seguinte estado do tempo para esta ... | https://investemoz.tempo.co.mz/o-instituto-nacional-de-meteorologia-preve-o-seguinte-estado-do-tempo-para-esta-29 | | Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada | Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. |
| 173474 | Sanções Disciplinares Aplicáveis a Trabalhadores Domésticos segundo o Regulamento de Moçambique | https://investemoz.tempo.co.mz/sancoes-disciplinares-aplicaveis-a-trabalhadores-domesticos-segundo-o-regulamento-de-mocambique | | Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada | Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. |
| 63248 | Dois alunos da 9ª classe caíram nas mãos da Polícia da República de Moçambique (... | https://investemoz.tempo.co.mz/dois-alunos-da-9a-classe-cairam-nas-maos-da-policia-da-republica-de-mocambique | | Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada | Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. |
| 173463 | Regulamento do Trabalho Doméstico | https://investemoz.tempo.co.mz/regulamento-do-trabalho-domestico | | Privado em QA; edição, leitura e prévia disponíveis; entrega privada preparada | Revisão editorial pela equipa; integração Git e transporte privado autenticado para homologação; publicação separada pelo portal. Imagens não verificadas mantêm referência/aviso. |
| 62688 | O Instituto Nacional de Meteorologia prevê o seguinte estado do tempo para esta ... | https://investemoz.tempo.co.mz/o-instituto-nacional-de-meteorologia-preve-o-seguinte-estado-do-tempo-para-esta-29 | https://teste-investidor.tempo.co.mz/management/portal-services/resources/48/article/preview | Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos | Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. |
| 173474 | Sanções Disciplinares Aplicáveis a Trabalhadores Domésticos segundo o Regulamento de Moçambique | https://investemoz.tempo.co.mz/sancoes-disciplinares-aplicaveis-a-trabalhadores-domesticos-segundo-o-regulamento-de-mocambique | https://teste-investidor.tempo.co.mz/management/portal-services/resources/58/article/preview | Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos | Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. |
| 63248 | Dois alunos da 9ª classe caíram nas mãos da Polícia da República de Moçambique (... | https://investemoz.tempo.co.mz/dois-alunos-da-9a-classe-cairam-nas-maos-da-policia-da-republica-de-mocambique | https://teste-investidor.tempo.co.mz/management/portal-services/resources/49/article/preview | Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos | Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. |
| 173463 | Regulamento do Trabalho Doméstico | https://investemoz.tempo.co.mz/regulamento-do-trabalho-domestico | https://teste-investidor.tempo.co.mz/management/portal-services/resources/59/article/preview | Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos | Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas. |
File diff suppressed because one or more lines are too long
+5 -27
View File
@@ -1,4 +1,3 @@
import { Link } from '@inertiajs/react';
import type { ContextReference } from '@/lib/investor-context';
export default function ContextDocuments({
@@ -6,49 +5,28 @@ export default function ContextDocuments({
}: {
source?: ContextReference;
}) {
if (!source) return null;
if (!source?.documents?.length) return null;
return (
<section
aria-label="Fonte e documentos do contexto"
aria-label="Documentos de apoio"
className="space-y-2 rounded border p-4"
>
<h2 className="font-semibold">Fonte e documentos do contexto</h2>
<p>
<Link className="underline" href={source.href}>
{source.title}
</Link>{' '}
· versão da oportunidade {source.version ?? 'não indicada'} ·{' '}
{source.visibility === 'private'
? 'Preparação privada — não publicada'
: 'Referência pública'}
</p>
<h2 className="font-semibold">Documentos de apoio</h2>
{(source.documents ?? []).map((doc) => (
<article
key={doc.support_id}
className="space-y-1 rounded border p-3"
>
<p>
{doc.title} · {doc.edition ?? 'Edição não indicada'} ·
versão documental {doc.document_version} · metadados v
{doc.metadata_version}
{doc.title}{doc.edition ? ` · ${doc.edition}` : ''}
</p>
<p className="break-all">SHA256: {doc.sha256}</p>
{doc.section && <p>Referência utilizada: {doc.section}</p>}
<p>{doc.source_reference}</p>
<a className="underline" href={doc.download_url}>
Consultar original autorizado —{' '}
Consultar documento —{' '}
{doc.edition ?? doc.title}
</a>
</article>
))}
{!source.documents?.length && (
<p>Sem suporte da biblioteca associado nesta versão.</p>
)}
<p>
Referência guardada nesta versão do dossier. A consulta do
original continua sujeita às permissões actuais; esta ligação
não publica nem concede acesso adicional.
</p>
</section>
);
}
+23 -18
View File
@@ -7,6 +7,7 @@ import {
type ContextView,
} from '@/lib/investor-context';
import { inheritCompatibleFields } from '@/lib/context-inheritance';
import OpportunityChoices from './investment-opportunity-choices';
type Props = {
contextId?: string | null;
consumer: 'simulator' | 'land' | 'desk' | 'submission';
@@ -27,6 +28,10 @@ const empty = (v: unknown) =>
(Array.isArray(v) && v.length === 0);
const text = (v: unknown) =>
typeof v === 'string' ? v : JSON.stringify(v ?? '');
const displayField = (values: Record<string, unknown>, key: string) =>
key.endsWith('_id')
? text(values[`${key}_label`]) || (empty(values[key]) ? 'não informado' : 'Referência escolhida; designação por recuperar')
: text(values[key]) || 'não informado';
export function DossierPrefill(props: Props) {
const {
contextId,
@@ -104,7 +109,7 @@ export function DossierPrefill(props: Props) {
),
);
const differences = Object.keys(source).filter(
(k) => k !== 'context_applicability_snapshot' && text(props.values[k]) !== text(source[k]),
(k) => k !== 'context_applicability_snapshot' && !k.endsWith('_label') && text(props.values[k]) !== text(source[k]),
);
const linkedRevision =
consumer === 'simulator'
@@ -117,39 +122,37 @@ export function DossierPrefill(props: Props) {
aria-label="Dados do dossier neste formulário"
>
<h2 className="font-bold">
Mesmo investimento · contexto v{view.context.revision}
Dados do seu investimento
</h2>
<p>
UUID {contextId} ·{' '}
<Link
className="underline"
href={`/investimentos/${contextId}`}
>
recuperar dossier
{view.case.title}
</Link>
. Mestres, declarações deste formulário e conteúdo já submetido
mantêm-se separados.
{' · '}<Link className="underline" href={`/investimentos/${contextId}`}>Voltar ao dossier</Link>
</p>
<InvestmentContextAssessment contextId={contextId} raw={view.prefill.simulator?.context_applicability_snapshot} />
{stale && (
<p className="font-semibold" role="status">
{linkedRevision > 0
? `O contexto do dossier difere da revisão vinculada a este registo (v${linkedRevision}).`
? 'Os dados do dossier mudaram desde a última análise.'
: 'Confira os valores deste formulário para o contexto atual; não há uma revisão anterior vinculada a estas entradas.'}{' '}
Os valores e resultados anteriores permanecem guardados;
confira os pressupostos antes de os usar no cenário atual.
</p>
)}
{message && <p role="status">{message}</p>}
{Object.values(view.context.selection).map(
(r) =>
{consumer === 'simulator' && !!view.context.opportunity_choices?.length && <OpportunityChoices choices={view.context.opportunity_choices}/>}
{Object.entries(view.context.selection)
.filter(([slot]) => consumer !== 'simulator' || !view.context.opportunity_choices?.length || !['opportunity', 'territory'].includes(slot))
.map(([, r]) =>
r && (
<p key={r.key}>
Fonte disponível:{' '}
<Link className="underline" href={r.href}>
{r.title}
</Link>{' '}
· {r.kind} · v{r.version ?? 'não exposta'}. {r.key}
</Link>
</p>
),
)}
@@ -214,11 +217,10 @@ export function DossierPrefill(props: Props) {
</label>
</td>
<td className="p-2 [overflow-wrap:anywhere]">
{text(props.values[k]) ||
'não informado'}
{displayField(props.values, k)}
</td>
<td className="p-2 [overflow-wrap:anywhere]">
{text(source[k]) || 'não informado'}
{displayField(source, k)}
</td>
</tr>
))}
@@ -232,17 +234,20 @@ export function DossierPrefill(props: Props) {
onClick={() => {
const patch: Record<string, unknown> = {};
for (const k of selected)
{
patch[k] = source[k] ?? '';
if (`${k}_label` in source) patch[`${k}_label`] = source[`${k}_label`];
}
const scope = `dossier:${contextId}:${consumer}`;
let raw = String(props.values.context_reuse ?? '');
try {
const metadata = JSON.parse(raw);
if(metadata.scope === scope) {
for(const key of selected) delete metadata.inherited?.[key];
for(const key of Object.keys(patch)) delete metadata.inherited?.[key];
raw=JSON.stringify(metadata);
}
} catch { /* Legacy metadata does not grant ownership of other fields. */ }
const adopted=inheritCompatibleFields(scope,{...props.values,...Object.fromEntries(selected.map(key=>[key,undefined]))},patch,raw,props.allowedFields);
const adopted=inheritCompatibleFields(scope,{...props.values,...Object.fromEntries(Object.keys(patch).map(key=>[key,undefined]))},patch,raw,props.allowedFields);
props.onApply({...patch,...(props.persistInheritance === false ? {} : {context_reuse:adopted.metadata})}, true);
setSelected([]);
setReviewed(false);
@@ -256,7 +261,7 @@ export function DossierPrefill(props: Props) {
</button>
</details>
)}
{!readOnly && reviewed && <p>Contexto v{view.context.revision} recuperado neste formulário, sem nova confirmação dos dados compatíveis. As edições locais conservam-se; as declarações e submissões formais mantêm os seus controlos próprios.</p>}
{!readOnly && reviewed && <p>Os dados disponíveis foram recuperados. Preencha o que falta; as suas alterações conservam-se.</p>}
<p className="text-xs">
Associar uma referência não confirma incentivos, licenças, terra
disponível, reserva ou autorização.
@@ -61,6 +61,8 @@ export function InvestmentContextAssessment({
if (!snapshot) return null;
const fields = snapshot.fields ?? {};
const zone = snapshot.zone;
const steps = (snapshot.path?.steps ?? []).filter(step => !!step.title?.trim());
if (!zone && !fields.investor_origin && !fields.tax_residency_country && !snapshot.preparation?.enabled) return null;
const regime = zone?.fields?.investment_regime ?? fields.investment_regime;
const names: Record<string, string> = {
zee: 'ZEE',
@@ -73,10 +75,9 @@ export function InvestmentContextAssessment({
aria-label="Aplicabilidade ao investimento"
>
<h3 className="font-semibold">
Aplicabilidade ao investimento · contexto v
{snapshot.context_revision}
Condições do investimento
</h3>
<p>
{(fields.investor_origin || fields.tax_residency_country) && <p>
Origem do investidor:{' '}
{{
national: 'nacional',
@@ -84,9 +85,8 @@ export function InvestmentContextAssessment({
mixed: 'misto',
}[fields.investor_origin] ?? 'informação em falta'}
. Residência fiscal declarada:{' '}
{fields.tax_residency_country || 'informação em falta'}. Estas
condições são distintas; uma não determina a outra.
</p>
{fields.tax_residency_country || 'Por preencher'}.
</p>}
{zone ? (
<>
<p>
@@ -95,8 +95,7 @@ export function InvestmentContextAssessment({
{names[regime ?? ''] ?? 'zona seleccionada'}.
</strong>{' '}
<Link className="underline" href={zone.href}>
{zone.title} · versão {zone.version} · consultar
ficha e fundamentos
{zone.title} · consultar condições
</Link>
.
</p>
@@ -162,39 +161,19 @@ export function InvestmentContextAssessment({
</p>
)}
<p role="status">
Benefício fiscal deste enquadramento: indeterminado. Não
existe neste consumidor uma regra executável ligada ao
reconhecimento e condições da zona. No simulador, o IVA
disponível corresponde apenas ao cenário geral sem
benefício, sobre vendas declaradas compatíveis; não
simula o regime especial. O cenário com benefício e o
apuramento final estão indisponíveis.
Os benefícios desta zona ainda não estão disponíveis no simulador. O cenário geral disponível não inclui benefícios especiais nem apuramento fiscal final.
</p>
</>
) : (
<p>
Enquadramento geral de preparação: sem ZEE, ZFI ou parque
seleccionado. Isso não confirma a elegibilidade para uma
operação fiscal. O cálculo mantém o âmbito e as exclusões da
versão publicada.
</p>
)}
<p>
Confirmado neste quadro significa apenas dado recuperado do
dossier; declarações e referências não são decisões jurídicas.
Requisitos específicos não documentados permanecem
indeterminados.
</p>
{snapshot.path && (
) : null}
{!!steps.length && (
<details>
<summary>
Passos adaptados pelo percurso existente · versão{' '}
{snapshot.path.definition_version}
Etapas a preparar
</summary>
<ul className="list-disc pl-5">
{snapshot.path.steps.map((step) => (
{steps.map((step) => (
<li key={step.code}>
{step.code} — {step.title}:{' '}
{step.title}:{' '}
{step.context_review_required
? 'informação alterada; rever fundamento'
: step.applicability ===
@@ -224,7 +203,7 @@ export function InvestmentContextAssessment({
className="underline"
href={`/investimentos/${contextId}/contexto/editar`}
>
Alterar contexto e comparar impacto antes de guardar
Rever dados do investimento
</Link>
)}
</section>
@@ -0,0 +1,55 @@
import { Link } from '@inertiajs/react';
import { TerritoryFields } from './investment-territory';
import type { OpportunityChoice } from '@/lib/investor-context';
import { card, input } from '@/pages/investment/shared';
import { useEffect, useState } from 'react';
import type { TerritoryOption } from './investment-territory';
export type CorridorOption = {key:string;name:string;provinces:TerritoryOption[]};
function ChoiceCard({c,i,corridors,disabled,update,remove}:{c:OpportunityChoice;i:number;corridors:CorridorOption[];disabled:boolean;update:(change:Partial<OpportunityChoice>)=>void;remove?:()=>void}) {
const [notice,setNotice]=useState('');
const corridor=corridors.find(r=>r.key===c.corridor_key)||corridors.find(r=>r.name===c.coverage);
const path=c.territory_path??[];
function location(next:TerritoryOption[], key=corridor?.key) {
update({corridor_key:key,territory_path:next,territory:next.length?{key:'territory:'+next.at(-1)!.id,kind:'territory',id:next.at(-1)!.id,title:next.map(p=>p.name).join(' / '),href:'/oportunidades',version:null,sha256:'',fields:{}}:null});
}
useEffect(()=>{
if (!remove || !corridor) return;
if (path.length && !corridor.provinces.some(p=>p.id===path[0].id)) {
setNotice('A localização anterior foi retirada porque não pertence ao corredor escolhido. Escolha uma localização compatível.');
location(corridor.provinces.length===1?corridor.provinces:[]);
} else if (!path.length && corridor.provinces.length===1) {setNotice('Província seleccionada automaticamente: '+corridor.provinces[0].name+'.');location(corridor.provinces);}
},[corridor?.key,JSON.stringify(corridor?.provinces),path[0]?.id,!!remove]);
return <article className={card+' space-y-3'}>
<h3 className="font-semibold"><Link className="underline" href={c.opportunity.href}>{i+1}. {c.opportunity.title}</Link></h3>
{remove && <label className="block">Corredor para este investimento<select className={input} disabled={disabled||!corridors.length} value={corridor?.key??''} onChange={e=>{
const next=corridors.find(r=>r.key===e.target.value);
if(!next)return;
const compatible=!path.length||next.provinces.some(p=>p.id===path[0].id);
const kept=compatible?path:[];
location(kept.length?kept:next.provinces.length===1?next.provinces:[],next.key);
setNotice(!path.length?(next.provinces.length===1?'Província seleccionada automaticamente: '+next.provinces[0].name+'.':'Escolha uma das províncias deste corredor.'):compatible?'A localização compatível foi conservada.':'A província e os níveis territoriais incompatíveis foram retirados.');
}}><option value="">Seleccione um corredor</option>{corridors.map(r=><option key={r.key} value={r.key}>{r.name}</option>)}</select></label>}
{!remove&&c.coverage&&<p>Abrangência: {c.coverage}</p>}
{notice&&<p role="status">{notice}</p>}
<p>Localização: {c.territory?.title??'Por escolher'}</p>
{!!c.portfolio_origins?.length && <section aria-label="Associações escolhidas"><h4 className="font-semibold">Associações escolhidas</h4><ul>{c.portfolio_origins.map(origin=><li key={origin.id}><Link className="underline" href={origin.href}>{origin.title}</Link>{remove&&<button type="button" className="ml-3 underline" disabled={disabled} onClick={()=>update({portfolio_origins:c.portfolio_origins?.filter(r=>r.id!==origin.id)})}>Retirar associação</button>}</li>)}</ul></section>}
{remove&&<>
<fieldset disabled={disabled}><legend>Actividades que pretende realizar nesta oportunidade</legend>{Object.entries({production:'Produção agrícola',processing:'Transformação agroindustrial',trade:'Comercialização e exportação',transport:'Transporte e logística',water:'Captação/utilização de água',forestry:'Actividade florestal',animal:'Produção/serviços pecuários'}).map(([key,label])=><label key={key} className="mr-4 inline-block"><input type="checkbox" checked={c.activities?.includes(key)??false} onChange={e=>update({activities:e.target.checked?[...(c.activities??[]),key]:(c.activities??[]).filter(x=>x!==key)})}/>{label}</label>)}</fieldset>
<TerritoryFields initial={c.territory_path} corridor={corridor?.key} disabled={disabled} onChange={next=>location(next)}/>
<label className="block">Motivo da alternativa territorial<textarea className={input} disabled={disabled} value={c.location_reason} onChange={e=>update({location_reason:e.target.value})}/></label>
<label className="block"><input type="checkbox" disabled={disabled} checked={!!c.confirm_location_change} onChange={e=>update({confirm_location_change:e.target.checked})}/>Confirmo a alteração da localização anteriormente confirmada, quando existir.</label>
<button type="button" disabled={disabled} className="underline" onClick={remove}>Retirar esta ficha da escolha</button>
</>}
</article>;
}
export default function OpportunityChoices({choices, onChange, disabled = false, corridors=[]}: {choices: OpportunityChoice[]; onChange?: (choices: OpportunityChoice[]) => void; disabled?: boolean;corridors?:CorridorOption[]}) {
const update = (index: number, change: Partial<OpportunityChoice>) => onChange?.(choices.map((c,i) => i === index ? {...c,...change} : c));
return <section className="space-y-4" aria-label="Oportunidades e respectivas localizações">
<h2 className="text-xl font-bold">Oportunidades escolhidas e localização de cada ficha</h2>
<p>{onChange?'Escolha o corredor e confirme onde pretende realizar o investimento.':'Escolhas guardadas no dossier; cada oportunidade conserva a sua localização e associações.'}</p>
{choices.map((c,i)=><ChoiceCard key={c.opportunity.key} c={c} i={i} corridors={corridors} disabled={disabled} update={change=>update(i,change)} remove={onChange?()=>onChange(choices.filter((_,index)=>index!==i)):undefined}/>)}
</section>;
}
@@ -0,0 +1,22 @@
import { Link } from '@inertiajs/react';
import type { RequirementsAssessment } from '@/lib/investor-context';
import { card } from '@/pages/investment/shared';
export default function InvestmentRequirements({assessment, contextId, excludedSteps = []}: {assessment?: RequirementsAssessment | null; contextId: string; excludedSteps?: string[]}) {
if (!assessment?.requirements.length) return null;
const labels: Record<string,string> = {selected:'Localização escolhida; direito não inferido',missing:'Informação em falta',conditional:'Conforme as condições do projecto',declared:'Actividades declaradas; enquadrar modalidade',undetermined:'Procedimento específico por determinar'};
const groups = new Map<string, RequirementsAssessment['requirements']>();
for (const row of assessment.requirements) {
if (excludedSteps.includes(row.step)) continue;
groups.set(row.opportunity_key,[...(groups.get(row.opportunity_key)??[]),row]);
}
return <section className="space-y-4" aria-label="Regras e documentos por oportunidade"><h2 className="text-xl font-bold">Requisitos por oportunidade e contexto</h2>
<p>{assessment.fiscal_notice}</p>
{[...groups.entries()].map(([key, rows])=><details key={key} className={card}><summary className="cursor-pointer font-semibold">{rows[0].opportunity_title} · {rows.length} aspectos a conferir</summary>{rows.map(r=><article key={r.opportunity_key+':'+r.code} className="my-3 space-y-2 border-t pt-3">
<h3 className="font-semibold">{r.title} — {r.opportunity_title}</h3><p>{labels[r.state] ?? r.state}</p><p>Entidade: {r.authority}</p><p>{r.basis}</p>
<ul className="list-disc pl-5">{r.documents.map(d=><li key={d}>{d}</li>)}</ul>
<a className="underline" href={r.source_url}>Consultar fundamento e procedimento</a>{' · '}<Link className="underline" href={`/investimentos/${contextId}#${r.step}`}>Preparar e acompanhar em {r.step}</Link>
<p>{r.decision}</p>
</article>)}</details>)}
</section>;
}
@@ -189,11 +189,13 @@ export function TerritoryFields({
onChange,
endpoint = '/api/v1/investment/territory-options',
disabled = false,
corridor,
}: {
initial?: TerritoryOption[];
onChange?: (path: TerritoryOption[]) => void;
endpoint?: string;
disabled?: boolean;
corridor?: string | null;
}) {
const [path, setPath] = useState(initial);
useEffect(() => setPath(initial), [initial]);
@@ -219,6 +221,7 @@ export function TerritoryFields({
parent={path[index - 1]?.id ?? ''}
selected={path[index]}
endpoint={endpoint}
corridor={corridor}
onChange={(value) => {
const next = replaceTerritoryPath(
path,
@@ -252,6 +255,7 @@ export function TerritoryLevel({
endpoint = '/management/territory-options',
fieldError,
disabled = false,
corridor,
}: {
index: number;
parent: string;
@@ -260,6 +264,7 @@ export function TerritoryLevel({
endpoint?: string;
fieldError?: string;
disabled?: boolean;
corridor?: string | null;
}) {
const id = useId();
const [page, setPage] = useState(1);
@@ -290,6 +295,7 @@ export function TerritoryLevel({
page: String(page),
q: search,
});
if (corridor) params.set('corridor', corridor);
const response = await fetch(`${endpoint}?${params}`, {
signal: controller.signal,
headers: { Accept: 'application/json' },
@@ -313,7 +319,7 @@ export function TerritoryLevel({
clearTimeout(timer);
controller.abort();
};
}, [index, parent, page, enabled, endpoint, retry, search]);
}, [index, parent, page, enabled, endpoint, retry, search, corridor]);
return (
<div className="space-y-2">
{enabled && (
@@ -345,7 +351,7 @@ export function TerritoryLevel({
aria-invalid={Boolean(fieldError)}
aria-describedby={fieldError ? `${id}-error` : undefined}
className={control}
disabled={!enabled || loading}
disabled={!enabled || loading || !!error}
value={selected?.id ?? ''}
onChange={(e) =>
onChange(options.find((o) => o.id === e.target.value))
@@ -0,0 +1,51 @@
import { Link } from '@inertiajs/react';
import { useState } from 'react';
import { button, card, input } from '@/pages/investment/shared';
type Item = {id:number;title:string;slug:string;summary:string|null;province:string|null;district:string|null;chains:string[];stages:string[];maturity_label:string|null};
export type CorridorGroup = {id:string;name:string;count:number;locations:string[];opportunities:Item[]};
type Props = {groups:CorridorGroup[];selected:number[];onSelect:(id:number,checked:boolean)=>void;onCompare:(ids:number[])=>void;onClear:()=>void};
function Group({group,selected,onSelect}: {group:CorridorGroup} & Pick<Props,'selected'|'onSelect'>) {
const [province,setProvince]=useState('');
const [district,setDistrict]=useState('');
const [chain,setChain]=useState('');
const provinces=[...new Set(group.opportunities.map(r=>r.province).filter((s):s is string=>!!s))].sort();
const districts=[...new Set(group.opportunities.filter(r=>!province||r.province===province).map(r=>r.district).filter((s):s is string=>!!s))].sort();
const chains=[...new Set(group.opportunities.flatMap(r=>r.chains))].sort();
const rows=group.opportunities.filter(r=>(!province||r.province===province)&&(!district||r.district===district)&&(!chain||r.chains.includes(chain)));
return <section className={card+' space-y-4'} aria-label={group.name}>
<header><h2 className="text-2xl font-bold">{group.name}</h2><p>{group.count} oportunidades publicadas</p>
<p>{group.locations.length ? 'Localizações identificadas: '+group.locations.join(' · ') : 'Abrangência territorial por indicar.'}</p></header>
<div className="grid gap-3 md:grid-cols-3">
<label>Província<select className={input} value={province} onChange={e=>{setProvince(e.target.value);setDistrict('');}}><option value="">Todas as províncias</option>{provinces.map(p=><option key={p}>{p}</option>)}</select></label>
<label>Distrito<select className={input} value={district} onChange={e=>setDistrict(e.target.value)}><option value="">Todos os distritos</option>{districts.map(d=><option key={d}>{d}</option>)}</select></label>
<label>Cadeia de valor<select className={input} value={chain} onChange={e=>setChain(e.target.value)}><option value="">Todas as cadeias</option>{chains.map(c=><option key={c}>{c}</option>)}</select></label>
</div>
<p role="status">{rows.length} oportunidades com estes filtros</p>
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">{rows.map(r=><article key={r.id} className="space-y-3 rounded-lg border p-4">
<h3 className="text-lg font-semibold"><Link href={'/oportunidades/'+r.slug}>{r.title}</Link></h3>
<p>{[r.province,r.district].filter(Boolean).join(' / ')||'Localização por escolher'}</p>
<p>Cadeia de valor: {r.chains.join(' · ')||'Por indicar'}</p>
{r.summary && !r.stages.includes(r.summary) && <p>{r.summary}</p>}
<p>Etapas e actividades: {r.stages.join(' · ')||'Por indicar'}</p><p>{r.maturity_label}</p>
<label className="flex gap-2"><input type="checkbox" checked={selected.includes(r.id)} onChange={e=>onSelect(r.id,e.target.checked)}/>Escolher para preparar investimento</label>
<Link className="underline" href={'/oportunidades/'+r.slug}>Ver ficha</Link>
</article>)}</div>
</section>;
}
export default function OpportunityCorridorGroups({groups,selected,onSelect,onCompare,onClear}:Props) {
return <div className="space-y-6">
<section className={card+' space-y-3'} aria-label="Escolhas entre corredores">
<h2 className="text-xl font-bold">Explore por corredor e escolha onde investir</h2>
<p>Pode escolher oportunidades de vários corredores e confirmar a localização de cada uma durante a preparação.</p>
<p>{selected.length} oportunidades escolhidas</p>
{!!selected.length&&<div className="flex flex-wrap gap-4"><Link className={button} href={'/investimentos/iniciar?'+selected.map(id=>'opportunities[]='+id).join('&')}>Preparar investimento com as oportunidades escolhidas</Link>
<button className={button} disabled={selected.length>4} onClick={()=>onCompare(selected)}>Comparar as escolhidas</button>
<button className="underline" onClick={onClear}>Limpar escolha</button>
{selected.length>4&&<p>A comparação apresenta até quatro fichas de cada vez. Pode preparar o investimento com todas as escolhidas.</p>}</div>}
</section>
{groups.map(group=><Group key={group.id} group={group} selected={selected} onSelect={onSelect}/>)}
</div>;
}
+32 -9
View File
@@ -1,6 +1,8 @@
import { simulationDraftEtag } from '@/lib/simulation-draft-etag';
import { SimulationPublishedIva, type PublishedIvaInput } from './simulation-published-iva';
import { SimulationMonthlyIva } from './simulation-monthly-iva';
import { inheritSimulatorFields } from '@/lib/context-inheritance';
import { inheritSimulatorFields, reusedInvestmentValue } from '@/lib/context-inheritance';
import { investmentLandPreparation } from '@/lib/investment-land-preparation';
import {
PublicDocumentContent,
type PublicDocument,
@@ -807,6 +809,7 @@ export function SimulationForm() {
const canSavePersonalData = Boolean(auth?.user);
const canAssociateDraft = Boolean(auth?.user?.email_verified_at);
const [draft, setDraft] = useState<SimulationDraft>({});
const [editingReused, setEditingReused] = useState<string[]>([]);
const [ready, setReady] = useState(false);
useEffect(() => { if (ready) setDraft(current => inheritSimulatorFields(current)); }, [draft, ready]);
const [contextReview, setContextReview] = useState({
@@ -1246,7 +1249,7 @@ export function SimulationForm() {
if (payload.data) {
if (cancelled) return;
draftIdRef.current = payload.data.id;
draftEtagRef.current = response.headers.get('ETag');
draftEtagRef.current = simulationDraftEtag(response.headers.get('ETag'));
setDraftVersionNo(payload.data.version_no);
setDraftExpiresAt(payload.data.expires_at ?? null);
if (canSavePersonalData)
@@ -1368,7 +1371,7 @@ export function SimulationForm() {
if (currentDraft) {
draftIdRef.current = currentDraft.id;
draftEtagRef.current =
currentResponse.headers.get('ETag');
simulationDraftEtag(currentResponse.headers.get('ETag'));
sessionStorage.setItem(draftIdKey, currentDraft.id);
}
}
@@ -1430,7 +1433,7 @@ export function SimulationForm() {
);
draftIdRef.current = payload.data.id;
setServerFieldErrors({});
draftEtagRef.current = response.headers.get('ETag');
draftEtagRef.current = simulationDraftEtag(response.headers.get('ETag'));
setDraftVersionNo(payload.data.version_no);
lastSavedFingerprintRef.current = fingerprint;
sessionStorage.setItem(draftIdKey, payload.data.id);
@@ -1591,7 +1594,7 @@ export function SimulationForm() {
)
throw new Error('Não foi possível abrir este dossier privado.');
draftIdRef.current = payload.data.id;
draftEtagRef.current = response.headers.get('ETag');
draftEtagRef.current = simulationDraftEtag(response.headers.get('ETag'));
setDraftVersionNo(payload.data.version_no);
setDraftExpiresAt(null);
setSelectedAccountDraftId(payload.data.id);
@@ -1654,7 +1657,7 @@ export function SimulationForm() {
);
const record = (payload as { data: ApiSimulationDraftV1 }).data;
draftIdRef.current = record.id;
draftEtagRef.current = response.headers.get('ETag');
draftEtagRef.current = simulationDraftEtag(response.headers.get('ETag'));
setDraftVersionNo(record.version_no);
setDraftExpiresAt(null);
setSelectedAccountDraftId(record.id);
@@ -1754,7 +1757,7 @@ export function SimulationForm() {
(payload as { expires_at: string }).expires_at,
);
draftEtagRef.current =
response.headers.get('ETag') ?? draftEtagRef.current;
simulationDraftEtag(response.headers.get('ETag')) ?? draftEtagRef.current;
}
setContinueLaterSaved(true);
setSaveStatus(
@@ -2373,7 +2376,7 @@ export function SimulationForm() {
);
const draftId = draftIdValue;
draftIdRef.current = draftId;
draftEtagRef.current = currentResponse.headers.get('ETag');
draftEtagRef.current = simulationDraftEtag(currentResponse.headers.get('ETag'));
if (!draftEtagRef.current)
throw new Error(
'O servidor não devolveu a versão actual do rascunho. Recarregue e tente novamente.',
@@ -2407,7 +2410,7 @@ export function SimulationForm() {
throw new Error(
'O servidor devolveu uma confirmação de anexação inválida.',
);
draftEtagRef.current = response.headers.get('ETag');
draftEtagRef.current = simulationDraftEtag(response.headers.get('ETag'));
const sources = payload.data?.simulation_sources;
if (Array.isArray(sources)) setSimulationSources(sources);
setDraft((currentDraft) => ({
@@ -3282,6 +3285,16 @@ export function SimulationForm() {
: 'Avalie um projecto definido. A análise fiscal acompanha os fluxos financeiros, mas não prova rentabilidade nem concede incentivos.'}
</output>
)}
{current.id === 'location' && contextId && (
<aside className="rounded border p-3" aria-label="Necessidade de terra">
<p>{investmentLandPreparation(draft.context_applicability_snapshot).need === 'no'
? 'Não precisa de terra neste investimento. A situação do terreno não é pedida; os valores anteriores ficam conservados.'
: investmentLandPreparation(draft.context_applicability_snapshot).need === 'yes'
? 'Precisa de terra neste investimento. Reveja a situação do terreno já preenchida ou indique-a quando disponível.'
: 'Falta definir se precisa de terra. Pode guardar o rascunho e continuar sem preencher a situação do terreno.'}</p>
<a className="underline" href={`/investimentos/${contextId}`}>Rever a necessidade de terra no dossier</a>
</aside>
)}
<div className="grid gap-4 sm:grid-cols-2">
{fields.map((field) => {
const fieldRequired =
@@ -3294,6 +3307,16 @@ export function SimulationForm() {
: Boolean(field.required);
const fieldError =
serverFieldErrors[field.key] ?? errors[field.key];
const reused = contextId && ready && !fieldError && !editingReused.includes(`${contextId}:${field.key}`)
? reusedInvestmentValue(`dossier:${contextId}:simulator`, field.key, draft) : null;
if (reused !== null) return (
<div key={field.key} id={`field-${field.key}`} className="rounded border bg-emerald-50 p-3">
<p className="font-semibold">{field.label}</p>
<p className="whitespace-pre-wrap [overflow-wrap:anywhere]">{reused}</p>
<p className="text-sm">Recuperado do dossier.</p>
<button type="button" className="underline" onClick={() => setEditingReused((keys) => [...keys, `${contextId}:${field.key}`])}>Alterar {field.label.toLocaleLowerCase('pt-MZ')}</button>
</div>
);
const ivaNotes = ivaIssues.filter((issue) => issue.field === field.key).map((issue, index) => <p key={index} role="note" className="mt-1 text-sm text-amber-950">{ivaIssueText(issue)}</p>);
if (field.options) {
const parentKey =
+50
View File
@@ -19,6 +19,22 @@ export function inheritCompatibleFields(scope:string, values:Record<string,unkno
const geoConflict=geo.some(key=>(!empty(values[key]) || key in previous) && signature(values[key])!==signature(source[key]) && previous[key]!==signature(values[key]));
for(const [key,value] of Object.entries(source)) {
if ((allowed && !allowed.includes(key)) || (empty(value) && !(key in previous))) continue;
if (key === 'context_applicability_snapshot') {
// Read-only assessment of the current dossier, not a deliberate form answer.
if (signature(values[key]) !== signature(value)) patch[key] = value;
next[key] = signature(value); inherited.push(key);
continue;
}
if (key.endsWith('_label')) {
const base = key.slice(0, -6);
const baseOwned = previous[base] === signature(values[base]);
const baseVacant = empty(values[base]) && !(base in previous);
if (!(base in source) || (geo.includes(base) && geoConflict) || (!baseOwned && !baseVacant && signature(values[base]) !== signature(source[base]))) continue;
// A label describes its identifier; it must never lag behind an adopted identifier.
if (signature(values[key]) !== signature(value)) patch[key] = value;
next[key] = signature(value); inherited.push(key);
continue;
}
const current=values[key];
const track = (item:unknown) => key==='parcels' ? Object.fromEntries(geo.map(field=>[field,(item as Array<Record<string,unknown>>)?.[0]?.[field] ?? ''])) : item;
const owned=previous[key]===signature(track(current));
@@ -42,6 +58,40 @@ export function inheritCompatibleFields(scope:string, values:Record<string,unkno
return {patch,metadata:JSON.stringify({version:1,scope,inherited:next}),inherited,conflicts};
}
/** Presentation only: a saved, unedited inherited value can be shown without asking again. */
export function reusedInvestmentValue(scope: string, key: string, values: Record<string, unknown>): string | null {
if (!['journey_type', 'purpose', 'project_title', 'project_summary', 'sector_id', 'province_id', 'district_id', 'admin_post_id', 'locality_id'].includes(key)) return null;
try {
const metadata = JSON.parse(String(values.context_reuse ?? ''));
if (metadata.version !== 1 || metadata.scope !== scope || empty(values[key]) || metadata.inherited?.[key] !== signature(values[key])) return null;
if (key.endsWith('_id') || ['journey_type', 'purpose'].includes(key)) {
const label = values[`${key}_label`];
return typeof label === 'string' && label.trim() && metadata.inherited?.[`${key}_label`] === signature(label) ? label : null;
}
return typeof values[key] === 'string' ? values[key] : null;
} catch { return null; }
}
/** Report the lineage without changing a stored answer or disclosing the tracking hash. */
export function investmentFieldOrigin(values: Record<string, unknown>, key: string, fallback: string): string {
try {
const reuse = JSON.parse(String(values.context_reuse ?? ''));
if (reuse.version !== 1 || !/^dossier:.+:simulator$/.test(reuse.scope) || !reuse.inherited?.[key]) return fallback;
const snapshot = JSON.parse(String(values.context_applicability_snapshot ?? ''));
const origin = snapshot.field_provenance?.[key];
const source = origin?.kind === 'opportunity' ? `oportunidade «${origin.title ?? 'referência guardada'}»`
: origin?.kind === 'process_declaration' || origin?.kind === 'dossier' ? 'dados introduzidos pelo utilizador no dossier'
: origin?.kind === 'sector' ? `catálogo sectorial «${origin.title ?? 'referência guardada'}»`
: origin?.kind === 'territory' ? `catálogo territorial «${origin.title ?? 'referência guardada'}»`
: origin?.kind === 'linked_simulation' ? 'simulador ligado ao mesmo dossier'
: 'dossier de investimento';
const version = origin?.version ? `, versão ${origin.version}` : '';
return reuse.inherited[key] === signature(values[key])
? `Herdado de ${source}${version}`
: `Alterado posteriormente pelo utilizador; origem anterior: ${source}${version}`;
} catch { return fallback; }
}
export function inheritSimulatorFields<T extends Record<string,unknown>>(draft:T): T {
const source:Record<string,unknown>={};
const plan=draft.business_plan_snapshot ? restoreBusinessPlanSnapshot(String(draft.business_plan_snapshot)) : null;
@@ -0,0 +1,19 @@
export type LandPreparation = { need: 'yes' | 'no' | 'unknown'; reason: string };
/** Preparation decision from the same saved dossier, never evidence of a land right. */
export function investmentLandPreparation(raw: unknown): LandPreparation {
try {
const snapshot = JSON.parse(String(raw ?? ''));
if (snapshot.version !== 1) return { need: 'unknown', reason: '' };
const decision = snapshot.land_preparation;
if (decision && ['yes', 'no', 'unknown'].includes(decision.need))
return { need: decision.need, reason: typeof decision.reason === 'string' ? decision.reason : '' };
// Existing snapshots already carry S10 applicability; no reimport is required.
const step = Array.isArray(snapshot.path?.steps) ? snapshot.path.steps.find((row: { code?: string } | null) => row?.code === 'S10') : null;
return { need: step?.applicability === 'not_needed_for_preparation' ? 'no' : step?.applicability === 'applicable' ? 'yes' : 'unknown', reason: typeof step?.reason === 'string' ? step.reason : '' };
} catch { return { need: 'unknown', reason: '' }; }
}
export function landStatusVisible(draft: Record<string, unknown>): boolean {
return !draft.context_applicability_snapshot || investmentLandPreparation(draft.context_applicability_snapshot).need === 'yes';
}
+23
View File
@@ -38,7 +38,26 @@ export type ContextReference = {
visibility?: 'private' | 'public';
state?: string | null;
};
export type RequirementsAssessment = {version: string; fiscal_notice: string; requirements: {opportunity_key: string; opportunity_title: string; code: string; title: string; state: string; step: string; authority: string; documents: string[]; basis: string; source_url: string; decision: string}[]};
export type OpportunityChoice = {
portfolio_origins?: {id:string;title:string;kind:string;href:string;version:number;sha256:string}[];
corridor_key?: string | null;
corridor_name?: string | null;
allowed_provinces?: import('@/components/investment-territory').TerritoryOption[];
activities?: string[];
territory_path?: import('@/components/investment-territory').TerritoryOption[];
confirm_location_change?: boolean;
opportunity: ContextReference;
territory: ContextReference | null;
location_reason: string;
location_state: string;
coverage?: string | null;
source?: string | null;
source_sha256?: string | null;
};
export type DossierContext = {
requirements_assessment?: RequirementsAssessment;
opportunity_choices?: OpportunityChoice[];
revision: number;
selection: Partial<
Record<
@@ -79,6 +98,8 @@ export type ContextView = {
notice: string;
};
export type ContextPlan = {
requirements_assessment?: RequirementsAssessment;
opportunity_choices?: OpportunityChoice[];
private_investment?: { before: Preparation; after: Preparation } | null;
preview_hash: string;
version: number;
@@ -102,8 +123,10 @@ export async function contextApi<T>(
url: string,
body?: unknown,
method = 'POST',
keepalive = false,
): Promise<T> {
const response = await fetch(url, {
keepalive,
method: body === undefined ? 'GET' : method,
credentials: 'same-origin',
headers: {
+13 -3
View File
@@ -229,8 +229,8 @@ function makeSections(
{
field: 'Projecto',
value: absent(text(draft, 'project_title')),
origin: 'Informação introduzida no dossier',
classification: 'Declarada pelo utilizador',
origin: rows.find(row => row[1] === 'Título')?.[3] ?? 'Informação introduzida no dossier',
classification: 'Origem indicada no dossier',
},
{
field: 'Data de geração',
@@ -316,7 +316,16 @@ function makeSections(
preparation.calculation_notice,
preparation.notice,
);
for (const requirement of preparation.requirements ?? [])
const omitted = (preparation.requirements ?? []).filter(
(requirement: { state: string }) => requirement.state === 'not_applicable',
);
if (omitted.length)
section?.paragraphs.push(
`Não exigido neste contexto: ${omitted.map((requirement: { label: string; reason: string }) => `${requirement.label} (${requirement.reason})`).join('; ')}.`,
);
for (const requirement of preparation.requirements ?? []) {
// Keep an explicitly associated historical document visible without requiring it again.
if (requirement.state === 'not_applicable' && !requirement.document_id && !requirement.document) continue;
section?.rows.push({
field: requirement.label,
value:
@@ -331,6 +340,7 @@ function makeSections(
classification:
'Preparação informativa — simulação, sem submissão à Administração',
});
}
}
if (zone?.href)
section?.rows.push({
+25 -1
View File
@@ -1,4 +1,6 @@
import { displayValue } from './display-value.ts';
import { investmentFieldOrigin } from './context-inheritance.ts';
import { investmentLandPreparation } from './investment-land-preparation.ts';
import { monthlyIvaInput, monthlyIvaKey, readMonthlyIvaRecord, readMonthlyIvaSettings } from './monthly-iva.ts';
import { modeloAPurchaseReferences } from './iva-modelo-a.ts';
import { fiscalQaRows } from './fiscal-qa-preview.ts';
@@ -686,7 +688,29 @@ export function buildProjectDossierRows(
]);
}
}
return rows;
if (draft.context_applicability_snapshot) {
try {
const context = JSON.parse(String(draft.context_applicability_snapshot));
if (context.version === 1 && Array.isArray(context.opportunity_choices)) for (const choice of context.opportunity_choices) {
if (typeof choice?.opportunity?.title !== 'string') continue;
rows.push(['Localização', 'Oportunidade escolhida', choice.opportunity.title, 'Referência escolhida no mesmo dossier']);
rows.push(['Localização', 'Localização da oportunidade', typeof choice.territory?.title === 'string' ? choice.territory.title : 'Por definir', choice.opportunity.title]);
if (Array.isArray(choice.portfolio_origins)) for (const origin of choice.portfolio_origins) {
if (typeof origin?.title !== 'string') continue;
rows.push(['Localização', 'Associação escolhida na carteira', origin.title, choice.opportunity.title+'; referência documental consultada, sem inferir implantação ou benefícios']);
}
}
} catch { /* An unreadable context remains indeterminate; do not reconstruct associations. */ }
const land = investmentLandPreparation(draft.context_applicability_snapshot);
rows.push(['Localização', 'Necessidade de terra', land.need === 'no' ? 'Não' : land.need === 'yes' ? 'Sim' : 'Por definir', land.reason || 'Decisão de preparação do dossier; não comprova direitos sobre a terra']);
if (land.need === 'yes') rows.push(['Localização', 'Situação do terreno', String(draft.land_status_label ?? ({ owned: 'Próprio', duat: 'DUAT', leased: 'Arrendado', requested: 'Pedido em curso', unknown: 'Por determinar' } as Record<string, string>)[String(draft.land_status)] ?? 'Informação em falta'), 'Informação declarada; não comprova título ou disponibilidade']);
else rows.push(['Localização', 'Preparação do terreno', land.need === 'no' ? 'Situação do terreno não aplicável à preparação actual; valores anteriores conservados.' : 'Definir se precisa de terra; pode continuar a preparar o investimento.', 'Sem inferência de dispensa legal']);
}
const lineageFields: Record<string, string> = {'Título':'project_title','Resumo':'project_summary','Sector':'sector_id','Província':'province_id','Distrito':'district_id','Posto administrativo':'admin_post_id','Localidade':'locality_id'};
return rows.map(row => {
const key = lineageFields[row[1]];
return key && row[2] ? [row[0], row[1], row[2], investmentFieldOrigin(draft, key, row[3])] : row;
});
}
export function exportProjectDossierCsv(
+10
View File
@@ -1,6 +1,16 @@
const levels = ['province_id', 'district_id', 'admin_post_id', 'locality_id'];
const searchRoutes = ['/carteira', '/oportunidades', '/zonas', '/educacao'];
/** Carry the explicitly consulted portfolio origin and search geography into the existing preparation entry. */
export function investmentPreparationHref(opportunity: number, pageUrl: string, origin?: string | null): string {
const source = new URL(pageUrl, 'https://portal.invalid');
const inherited = source.searchParams.has('catalogue_query') ? new URLSearchParams(source.searchParams.get('catalogue_query')!) : source.searchParams;
const query = new URLSearchParams({source: `opportunity:${opportunity}`});
if (origin) query.set('from_dossier', origin);
for (const field of levels) if (inherited.get(field)) query.set(field, inherited.get(field)!);
return '/investimentos/iniciar?' + query.toString();
}
/** URL-scoped search context, never a saved investment or another browser tab. */
export function searchTerritoryHref(href: string, pageUrl: string): string {
const base = 'https://portal.invalid';
@@ -0,0 +1,5 @@
/** Compression can mark the representation weak; the draft version token itself is unchanged. */
export function simulationDraftEtag(header: string | null): string | null {
if (header && /^W\/"sfd-[0-9a-f-]{36}-v[1-9][0-9]*"$/i.test(header)) return header.slice(2);
return header;
}
+2 -1
View File
@@ -1,4 +1,5 @@
import { inheritSimulatorFields } from './context-inheritance.ts';
import { landStatusVisible } from './investment-land-preparation.ts';
import { csvCell } from './csv.js';
import { fiscalQaRows } from './fiscal-qa-preview.ts';
import { isApiSimulationResultSnapshotV1 } from './simulation-contract.ts';
@@ -738,7 +739,7 @@ export const simulationFieldGroups: Record<
label: 'Situação do terreno',
options: 'land_status',
showWhen: (d) =>
d.purpose !== 'simulate_only' || isInvestorProjectJourney(d),
landStatusVisible(d) && (d.purpose !== 'simulate_only' || isInvestorProjectJourney(d)),
},
{
key: 'area_required',
+22 -5
View File
@@ -1,4 +1,5 @@
import EducationalGuides from '@/components/educational-guides';
import OpportunityCorridorGroups, { type CorridorGroup } from '@/components/opportunity-corridor-groups';
import { searchTerritoryHref } from '@/lib/search-territory';
import type { EducationalArticle } from '@/components/educational-article-card';
import OpportunitySearchFilters from '@/components/opportunity-search-filters';
@@ -25,8 +26,10 @@ export default function Catalogue({
territorial,
territorySelection,
catalogueUrl,
corridorGroups = [],
}: {
guides?: EducationalArticle[];
corridorGroups?: CorridorGroup[];
filters: Record<string, string | string[] | boolean>;
references: References;
results: {
@@ -49,6 +52,13 @@ export default function Catalogue({
}) {
const page = usePage<{ auth: { user: unknown } }>();
const { auth } = page.props;
const [investmentSelection, setInvestmentSelection] = useState<number[]>(() => {
try { return JSON.parse(sessionStorage.getItem('investment-opportunity-selection') ?? '[]'); } catch { return []; }
});
function selectInvestment(id: number, checked: boolean) {
const next = checked ? [...new Set([...investmentSelection, id])] : investmentSelection.filter(x => x !== id);
setInvestmentSelection(next); sessionStorage.setItem('investment-opportunity-selection', JSON.stringify(next));
}
const form = useForm({
association_type: String(filters.association_type ?? ''),
association_target: String(filters.association_target ?? ''),
@@ -116,6 +126,10 @@ export default function Catalogue({
const [saved, setSaved] = useState(favourites);
const [selected, setSelected] = useState<number[]>([]);
const [comparison, setComparison] = useState<{ record: Opportunity }[]>([]);
const comparisonRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (view === 'corridors' && comparison.length) comparisonRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });
}, [comparison, view]);
const [points, setPoints] = useState<Feature[] | null>(null);
const [areas, setAreas] = useState<InvestmentMapArea[]>([]);
const [unlocated, setUnlocated] = useState<
@@ -411,7 +425,7 @@ export default function Catalogue({
Alargar pesquisa (remover localização)
</button>
)}
{['grid', 'list', 'map', 'combined'].map((value, i) => (
{['grid', 'list', 'map', 'combined', 'corridors'].map((value, i) => (
<button
key={value}
disabled={searchLoading || !!searchError}
@@ -427,7 +441,7 @@ export default function Catalogue({
);
}}
>
{['Grelha', 'Lista', 'Mapa', 'Lista e mapa'][i]}
{['Grelha', 'Lista', 'Mapa', 'Lista e mapa', 'Por corredor'][i]}
</button>
))}
<a
@@ -561,7 +575,8 @@ export default function Catalogue({
</ul>
</section>
)}
{!searchLoading &&
{view === 'corridors' && !searchLoading && !searchError && <OpportunityCorridorGroups groups={corridorGroups} selected={investmentSelection} onSelect={selectInvestment} onClear={()=>{setInvestmentSelection([]);sessionStorage.removeItem('investment-opportunity-selection');}} onCompare={ids=>void api('/api/v1/investment/compare?'+ids.map(id=>'ids[]='+id).join('&')).then(data=>setComparison(data.records)).catch(e=>setError(e.message))}/>}
{view !== 'corridors' && !searchLoading &&
!searchError &&
(view !== 'map' || error || points?.length === 0) && (
<div
@@ -603,6 +618,7 @@ export default function Catalogue({
? 'Retirar favorito'
: 'Guardar favorito'}
</button>
<label className="flex gap-2"><input type="checkbox" checked={investmentSelection.includes(r.id)} onChange={e => selectInvestment(r.id,e.target.checked)} />Escolher para preparar investimento</label>
<label className="flex gap-2">
<input
type="checkbox"
@@ -639,7 +655,7 @@ export default function Catalogue({
.
</p>
)}
<nav aria-label="Paginação" className="flex gap-6">
<nav aria-label="Paginação" className={view === 'corridors' ? 'hidden' : 'flex gap-6'}>
{results.prev_page_url && (
<Link href={results.prev_page_url}>Anterior</Link>
)}
@@ -662,8 +678,9 @@ export default function Catalogue({
Comparar {selected.length} oportunidades
</button>
)}
{view !== 'corridors' && !!investmentSelection.length && <section className={card}><p>{investmentSelection.length} oportunidades escolhidas, conservadas ao mudar de página.</p><Link className={button} href={'/investimentos/iniciar?' + investmentSelection.map(id => 'opportunities[]='+id).join('&')}>Preparar investimento com as oportunidades escolhidas</Link><button className="ml-4 underline" onClick={() => {setInvestmentSelection([]);sessionStorage.removeItem('investment-opportunity-selection');}}>Limpar escolha</button></section>}
{comparison.length > 0 && (
<div className="overflow-x-auto">
<div ref={comparisonRef} className="overflow-x-auto">
<table className="w-full border-collapse text-left">
<caption className="p-3 text-left font-bold">
Comparação — montantes nas moedas de origem
+6 -10
View File
@@ -1,5 +1,5 @@
import EducationalGuides from '@/components/educational-guides';
import { searchTerritoryHref } from '@/lib/search-territory';
import { searchTerritoryHref, investmentPreparationHref } from '@/lib/search-territory';
import type { EducationalArticle } from '@/components/educational-article-card';
import { ClassificationSummary } from '@/components/opportunity-classification';
import { Link, router, usePage } from '@inertiajs/react';
@@ -32,9 +32,10 @@ export default function Detail({
const [sharing, setSharing] = useState('');
return (
<Frame title={r.title}>
{r.maturity_label === 'Em preparação' && <p className="rounded border border-amber-700 bg-amber-50 p-4"><strong>Em preparação.</strong> Consulte a oportunidade e prepare o seu investimento. A execução depende das condições e autorizações aplicáveis ao projecto.</p>}
<Link
className={button}
href={'/investimentos/iniciar?source=opportunity:' + r.id}
href={investmentPreparationHref(r.id, page.url, dossierOrigin?.id)}
>
Iniciar ou continuar investimento com esta oportunidade
</Link>
@@ -55,14 +56,6 @@ export default function Detail({
<p>
{r.reference} · {r.sector?.name} · {r.province} {r.district}
</p>
{!r.last_validated_at && (
<p className="rounded border border-amber-700 bg-amber-50 p-4">
Esta ficha não regista data de validação documental.
Confirme a origem e as condições antes de agir. Preparar uma
manifestação expressa interesse; não confirma a oferta nem
representa aprovação.
</p>
)}
<p className="text-xl">{r.summary}</p>
<ClassificationSummary rows={c.classification ?? []} />
<TerritorialSources sources={c.source_evidence} />
@@ -97,6 +90,9 @@ export default function Detail({
</p>
)}
<div className="flex flex-wrap gap-4">
<Link className={button} href={investmentPreparationHref(r.id, page.url, dossierOrigin?.id)}>
Iniciar ou continuar o roteiro deste investimento
</Link>
{auth.user ? (
<button
className={button}
@@ -2,42 +2,44 @@ import ContextDocuments from '@/components/context-documents';
import { Link } from '@inertiajs/react';
import { useState } from 'react';
import { Frame, card, input, button } from '@/pages/investment/shared';
import { contextApi, type ContextReference } from '@/lib/investor-context';
import { contextApi, type ContextReference, type OpportunityChoice } from '@/lib/investor-context';
import { createOperationKey } from '@/lib/operation-key';
export default function ContextStart({
contexts,
source,
opportunityChoices = [],
step = null,
entryQuery = {},
}: {
contexts: { id: string; title: string; version: number }[];
source: ContextReference | null;
opportunityChoices?: OpportunityChoice[];
step?: string | null;
entryQuery?: Record<string, string>;
}) {
const [chosen, setChosen] = useState('');
const [title, setTitle] = useState(source?.title ?? '');
const [title, setTitle] = useState(source?.title ?? opportunityChoices[0]?.opportunity.title ?? '');
const [consent, setConsent] = useState(false);
const [busy, setBusy] = useState(false);
const [error, setError] = useState('');
const [key] = useState(createOperationKey);
const suffix = source ? '?source=' + encodeURIComponent(source.key) : '';
const sourceSuffix = opportunityChoices.length ? '?' + opportunityChoices.map(c => `opportunities[]=${encodeURIComponent(c.opportunity.id)}`).join('&') : source ? '?source=' + encodeURIComponent(source.key) : '';
const suffix = sourceSuffix ? sourceSuffix+'&selection_key='+encodeURIComponent(key)+'&'+new URLSearchParams(entryQuery).toString() : '';
return (
<Frame title="Iniciar ou continuar um investimento">
{!!opportunityChoices.length && <section className={card}><h2 className="font-bold">Oportunidades escolhidas</h2><ul>{opportunityChoices.map(c => <li key={c.opportunity.key}><Link className="underline" href={c.opportunity.href}>{c.opportunity.title}</Link> · {c.territory?.title ?? 'Localização por escolher'}{c.portfolio_origins?.map(origin=><p key={origin.id}>Associação escolhida: <Link className="underline" href={origin.href}>{origin.title}</Link></p>)}</li>)}</ul><p>Confirme a localização de cada ficha na comparação do dossier. Ao continuar um dossier, as escolhas já guardadas conservam-se.</p></section>}
<p>
Ideia, oportunidade, sector e localização convergem no mesmo
dossier. A referência é para preparação e não confirma direitos
ou benefícios.
</p>
<ContextDocuments source={source ?? undefined} />
{source && (
{source && !opportunityChoices.length && (
<section className={card}>
<h2 className="font-bold">Origem consultada</h2>
<h2 className="font-bold">A sua escolha</h2>
<Link className="underline" href={source.href}>
{source.title}
</Link>
<p>
{source.kind} · v{source.version ?? 'não exposta'} ·{' '}
{source.key}
</p>
</section>
)}
<section className={card + ' space-y-3'}>
@@ -63,14 +65,14 @@ export default function ContextStart({
<Link
className={button}
href={
step && !source
step && !source && !opportunityChoices.length
? `/investimentos/${chosen}#${step}`
: `/investimentos/${chosen}/contexto/editar${suffix}`
}
>
{step && !source
{step && !source && !opportunityChoices.length
? 'Abrir ' + step + ' no mesmo dossier'
: 'Comparar no mesmo dossier'}
: 'Preparar neste dossier'}
</Link>
)}
<p>
@@ -100,7 +102,7 @@ export default function ContextStart({
>
<h2 className="text-xl font-bold">Iniciar uma nova ideia</h2>
<p>
Utiliza o cadastro e as cinco fases/19 etapas já existentes.
Organize a sua ideia e acompanhe a preparação do investimento.
</p>
<label className="grid gap-1">
Título do investimento
@@ -121,7 +123,7 @@ export default function ContextStart({
Autorizo reutilizar o meu perfil neste dossier privado.
</label>
<button className={button} disabled={!consent || busy}>
Criar dossier e comparar a referência
Criar dossier e preparar investimento
</button>
{error && <p role="alert">{error}</p>}
</form>
+112 -56
View File
@@ -2,10 +2,9 @@ import {
PreparationEditor,
PreparationImpact,
} from '@/components/private-investment-preparation';
import { InvestmentContextAssessment } from '@/components/investment-context-assessment';
import ContextDocuments from '@/components/context-documents';
import { Link } from '@inertiajs/react';
import { useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import { Frame, button, card, input } from '@/pages/investment/shared';
import {
TerritoryFields,
@@ -20,6 +19,10 @@ import {
} from '@/lib/investor-context';
import { useNavigationContext } from '@/components/navigation-context';
import { createOperationKey } from '@/lib/operation-key';
import OpportunityChoices from '@/components/investment-opportunity-choices';
import InvestmentRequirements from '@/components/investment-requirements';
import type { OpportunityChoice } from '@/lib/investor-context';
import type { CorridorOption } from '@/components/investment-opportunity-choices';
type Options = {
opportunities: { key: string; title: string; kind: string }[];
sectors: { id: number; name: string }[];
@@ -32,36 +35,93 @@ export default function ContextEditor({
options: initialOptions,
source,
territorySelection = [],
requestedChoices = null,
draft = null,
entrySources = [],
entryLocationDifferences = [],
}: {
initial: ContextView;
options: Options;
source: ContextReference | null;
territorySelection?: TerritoryOption[];
requestedChoices?: OpportunityChoice[] | null;
entrySources?: string[];
entryLocationDifferences?: {title:string;saved:string;requested:string}[];
draft?: {draft_sequence?:number;needs_review?:boolean;selection?:Partial<Record<Slot,string>>;declarations?:Record<string,string>;replace_fields?:string[];document_links?:Record<string,string|null>}|null;
}) {
const navigation = useNavigationContext();
const [choices, setChoices] = useState<OpportunityChoice[]>(requestedChoices ?? initial.context.opportunity_choices ?? []);
const [manageChoices, setManageChoices] = useState(requestedChoices !== null || !!initial.context.opportunity_choices?.length);
const [view, setView] = useState(initial);
const [preparation, setPreparation] = useState(initial.private_investment);
const [options, setOptions] = useState(initialOptions);
const [query, setQuery] = useState('');
const [changes, setChanges] = useState<Partial<Record<Slot, string>>>(() =>
source
draft?.selection ?? (source
? {
[source.kind === 'business' ? 'opportunity' : source.kind]:
source.key,
}
: {},
: {}),
);
const [declarations, setDeclarations] = useState<Record<string, string>>(
{},
draft?.declarations ?? {},
);
const [documentLinks, setDocumentLinks] = useState<
Record<string, string | null>
>({});
const [replace, setReplace] = useState<string[]>([]);
>(draft?.document_links ?? {});
const [replace, setReplace] = useState<string[]>(draft?.replace_fields ?? []);
const [plan, setPlan] = useState<ContextPlan | null>(null);
const [busy, setBusy] = useState(false);
const [error, setError] = useState('');
const [message, setMessage] = useState('');
const [corridors,setCorridors]=useState<CorridorOption[]>([]);
const [parks,setParks]=useState<Options['parks']>([]);
const [corridorLoading,setCorridorLoading]=useState(true);
const [corridorError,setCorridorError]=useState('');
const [corridorRetry,setCorridorRetry]=useState(0);
const [draftStatus,setDraftStatus]=useState('');
const [draftRetry,setDraftRetry]=useState(0);
const queue=useRef<Promise<unknown>>(Promise.resolve());
const firstCorridor=choices[0]?.corridor_key ?? corridors.find(c=>c.name===choices[0]?.coverage)?.key;
const firstTerritory=choices[0]?.territory?.id;
useEffect(()=>{
let active=true;setCorridorLoading(true);setCorridorError('');
const params=new URLSearchParams();if(firstCorridor)params.set('corridor',firstCorridor);if(firstTerritory)params.set('territory',firstTerritory);
void contextApi<{corridors:CorridorOption[];parks:Options['parks']}>('/investimentos/opcoes-corredor?'+params).then(data=>{
if(!active)return;setCorridors(data.corridors);setParks(data.parks);
const park=Object.hasOwn(changes,'park')?changes.park:view.context.selection.park?.key;
if(firstCorridor&&park&&!data.parks.some(p=>'park:'+p.id===park)){
setChanges(old=>({...old,park:''}));setPlan(null);setMessage('A zona ou parque anterior foi retirado porque não tem enquadramento compatível com a localização escolhida.');
}
}).catch(e=>{if(active)setCorridorError(e.message)}).finally(()=>{if(active)setCorridorLoading(false)});
return()=>{active=false};
},[firstCorridor,firstTerritory,corridorRetry]);
const draftBody=JSON.stringify({entry_sources:entrySources,version:view.case.version,selection:changes,declarations,replace_fields:replace,document_links:documentLinks,
...(manageChoices?{opportunity_choices:choices.map(c=>({source:c.opportunity.key,portfolio_origins:(c.portfolio_origins??[]).map(r=>r.id),corridor_key:c.corridor_key,territory:c.territory?.key??null,location_reason:c.location_reason,confirm_location_change:!!c.confirm_location_change,activities:c.activities??[]}))}:{})});
const latestDraft=useRef(draftBody);latestDraft.current=draftBody;
const skipNextDraft=useRef(false);
const confirmedDraftBody=useRef<string|null>(null);
const sequence=useRef({body:'',value:Math.max(Date.now(),draft?.draft_sequence??0)});
if(sequence.current.body!==draftBody)sequence.current={body:draftBody,value:Math.max(Date.now(),sequence.current.value+1)};
useEffect(()=>{
if(busy||corridorLoading||corridorError)return;
if(skipNextDraft.current){skipNextDraft.current=false;confirmedDraftBody.current=draftBody;setDraftStatus('Dados confirmados.');return;}
if(draftBody===confirmedDraftBody.current)return;
setDraftStatus('A guardar rascunho…');
const timer=setTimeout(()=>{
const savedSequence=sequence.current.value;
queue.current=queue.current.catch(()=>{}).then(()=>contextApi('/investimentos/'+view.case.id+'/contexto/rascunho',{...JSON.parse(draftBody),draft_sequence:savedSequence},'POST',draftBody.length<60000))
.then(()=>{if(latestDraft.current===draftBody)setDraftStatus('Rascunho guardado.');})
.catch(e=>{if(latestDraft.current===draftBody)setDraftStatus('Rascunho não guardado: '+e.message)});
},400);
return()=>clearTimeout(timer);
},[draftBody,busy,corridorLoading,corridorError,draftRetry]);
useEffect(()=>{
const flush=()=>{const body=latestDraft.current;if(body!==confirmedDraftBody.current&&body.length<60000)void contextApi('/investimentos/'+initial.case.id+'/contexto/rascunho',{...JSON.parse(body),draft_sequence:sequence.current.value},'POST',true).catch(()=>{});};
window.addEventListener('pagehide',flush);
return()=>{window.removeEventListener('pagehide',flush);flush();};
},[initial.case.id]);
const [operation, setOperation] = useState(createOperationKey);
const [requestBody, setRequestBody] = useState<Record<
string,
@@ -107,6 +167,7 @@ export default function ContextEditor({
setError('');
setMessage('');
const body = {
...(manageChoices ? {opportunity_choices: choices.map(c => ({source:c.opportunity.key,portfolio_origins:(c.portfolio_origins??[]).map(r=>r.id),corridor_key:c.corridor_key,territory:c.territory?.key ?? null,location_reason:c.location_reason,confirm_location_change:!!c.confirm_location_change,activities:c.activities??[]}))} : {}),
version: view.case.version,
selection: changes,
declarations,
@@ -134,6 +195,7 @@ export default function ContextEditor({
async function confirm() {
if (!plan || !requestBody) return;
setBusy(true);
await queue.current.catch(()=>{});
setError('');
try {
await contextApi(
@@ -148,7 +210,11 @@ export default function ContextEditor({
const next = await contextApi<ContextView>(
`/investimentos/${view.case.id}/contexto`,
);
skipNextDraft.current=true;
// The entry has been consumed: refreshing must recover saved choices, not reapply a removed association.
window.history.replaceState(window.history.state, '', `/investimentos/${view.case.id}/contexto/editar${window.location.hash}`);
setView(next);
setChoices(next.context.opportunity_choices ?? []);
setPreparation(next.private_investment);
navigation.refresh();
setChanges({});
@@ -158,7 +224,7 @@ export default function ContextEditor({
setPlan(null);
setOperation(createOperationKey());
setMessage(
'Contexto guardado no mesmo dossier. Resultados afetados identificados para revisão; conteúdo submetido e histórico preservados.',
'Dados guardados. Pode continuar a preparação do investimento.',
);
} catch (e) {
setError(
@@ -171,28 +237,19 @@ export default function ContextEditor({
}
}
return (
<Frame title="Contexto do investimento">
<InvestmentContextAssessment
contextId={view.case.id}
raw={view.prefill.simulator?.context_applicability_snapshot}
/>
<ContextDocuments source={view.context.selection.opportunity} />
<Frame title="Preparar investimento">
<div className="space-y-5 [overflow-wrap:anywhere]">
<Link
className="underline"
href={`/investimentos/${view.case.id}`}
>
Voltar ao mesmo dossier
Voltar ao dossier
</Link>
<p>
{view.case.title} · UUID {view.case.id} · dossier v
{view.case.version} · contexto v{view.context.revision}
</p>
<p className="rounded border border-emerald-300 bg-emerald-50 p-3">
As referências apoiam a preparação. Associar uma
oportunidade, sector ou localização não confirma incentivos,
licença, terra disponível, reserva ou autorização.
{view.case.title}
</p>
<p>Confirme as oportunidades e a localização, complete os dados disponíveis e reveja antes de guardar.</p>
{!!entryLocationDifferences.length && <aside className="rounded border border-amber-500 p-3" aria-label="Localização anterior conservada">{entryLocationDifferences.map(row=><p key={row.title}>{row.title}: a localização recebida ({row.requested}) não substituiu a escolha guardada ({row.saved}). Pode rever a localização abaixo antes de confirmar.</p>)}</aside>}
{error && (
<div
role="alert"
@@ -210,8 +267,17 @@ export default function ContextEditor({
)}
{message && <p role="status">{message}</p>}
<section className={card + ' space-y-4'}>
{corridorError&&<p role="alert">{corridorError} <button className="underline" onClick={()=>setCorridorRetry(v=>v+1)}>Tentar novamente</button></p>}
{corridorLoading&&<p role="status">A carregar opções do corredor…</p>}
<p role="status">{draftStatus}</p>
{draft?.needs_review&&<p role="status">O rascunho foi recuperado, mas o dossier recebeu outras alterações. Reveja os dados antes de confirmar.</p>}
{draftStatus.startsWith('Rascunho não guardado')&&<button type="button" className="underline" onClick={()=>setDraftRetry(v=>v+1)}>Tentar guardar novamente</button>}
<OpportunityChoices corridors={corridors} choices={choices} disabled={busy||corridorLoading||!!corridorError} onChange={next=>{setChoices(next);setManageChoices(true);setPlan(null);}}/>
<ContextDocuments source={source ?? view.context.selection.opportunity} />
{view.context.revision > 0 && !Object.keys(changes).length && JSON.stringify(choices) === JSON.stringify(view.context.opportunity_choices ?? []) && <InvestmentRequirements assessment={view.context.requirements_assessment} contextId={view.case.id}/>}
<label className="block">Acrescentar uma oportunidade à escolha<select className={input} value="" onChange={e=>{const o=options.opportunities.find(o=>o.key===e.target.value);if(o && !choices.some(c=>c.opportunity.key===o.key)){setChoices([...choices,{opportunity:{...o,id:o.key.split(':')[1],href:'/oportunidades',version:null,sha256:'',fields:{}},territory:null,location_reason:'',location_state:'undetermined'}]);setManageChoices(true);setPlan(null);}}}><option value="">Seleccionar ficha</option>{options.opportunities.filter(o=>o.kind==='opportunity'&&!choices.some(c=>c.opportunity.key===o.key)).map(o=><option key={o.key} value={o.key}>{o.title}</option>)}</select></label>
<h2 className="text-xl font-bold">
Escolher referências existentes
Dados do investimento
</h2>
<div className="flex flex-wrap gap-2">
<label className="min-w-0 flex-1">
@@ -241,11 +307,11 @@ export default function ContextEditor({
</button>
</div>
<label className="grid min-w-0 gap-1">
Oportunidade
Referência individual (sem selecção múltipla)
<select
className={input + ' max-w-full min-w-0'}
value={selected('opportunity')}
disabled={busy}
disabled={busy || manageChoices}
onChange={(e) =>
change('opportunity', e.target.value)
}
@@ -331,29 +397,30 @@ export default function ContextEditor({
</select>
</label>
<label className="grid min-w-0 gap-1">
ZEE, ZFI ou parque consultado
{choices.length>1?'Zona ou parque da primeira oportunidade':'ZEE, ZFI ou parque consultado'}
<select
className={input + ' max-w-full min-w-0'}
disabled={busy}
disabled={busy||corridorLoading||!!corridorError||(!!firstCorridor&&!firstTerritory)}
value={selected('park')}
onChange={(e) => change('park', e.target.value)}
>
<option value="">
Sem ZEE, ZFI ou parque seleccionado
</option>
{options.parks.map((p) => (
{(firstCorridor?parks:options.parks).map((p) => (
<option key={p.id} value={'park:' + p.id}>
{p.name} · v{p.version_no}
{p.name}
</option>
))}
</select>
</label>
<h3 className="font-semibold">
{firstCorridor&&!corridorLoading&&!corridorError&&<p>{!firstTerritory?'Escolha a localização para consultar zonas e parques.':!parks.length?'Não existem zonas ou parques com enquadramento documentado compatível nesta localização.':'A consulta não confirma disponibilidade de lotes, admissão ou incentivos.'}</p>}
{!manageChoices&&<><h3 className="font-semibold">
Localização — escolher até ao nível conhecido
</h3>
<TerritoryFields
initial={territorySelection}
disabled={busy}
disabled={busy || manageChoices}
endpoint="/api/v1/investment/territory-options"
onChange={(path) =>
change(
@@ -367,10 +434,10 @@ export default function ContextEditor({
{view.context.selection.territory && (
<p>
Referência guardada:{' '}
{view.context.selection.territory.title} · v
{view.context.selection.territory.version}.
{view.context.selection.territory.title}
</p>
)}
</>}
</section>
<section className={card + ' space-y-3'}>
<h2 className="text-xl font-bold">
@@ -563,7 +630,7 @@ export default function ContextEditor({
disabled={busy}
onClick={() => void preview()}
>
Comparar impacto antes de guardar
Rever dados antes de guardar
</button>
</section>
{plan && (
@@ -574,6 +641,8 @@ export default function ContextEditor({
<h2 className="text-xl font-bold">
Comparação antes da confirmação
</h2>
{!!plan.opportunity_choices?.length && <OpportunityChoices choices={plan.opportunity_choices}/>}
<InvestmentRequirements assessment={plan.requirements_assessment} contextId={view.case.id}/>
<div className="overflow-x-auto">
<table className="w-full min-w-[36rem] table-fixed text-left">
<thead>
@@ -588,10 +657,9 @@ export default function ContextEditor({
Mantido
</th>
<td className="p-2">
UUID, identidade e{' '}
{plan.documents_preserved}{' '}
documentos.{' '}
{plan.maintained.map((x) => (
{plan.maintained.filter(x=>!x.field.endsWith('_id')).map((x) => (
<p key={x.field}>
{contextFieldNames[
x.field
@@ -607,7 +675,7 @@ export default function ContextEditor({
Atualizado pela nova seleção
</th>
<td className="p-2">
{plan.updated.map((x) => (
{plan.updated.filter(x=>!x.field.endsWith('_id')).map((x) => (
<p key={x.field}>
{contextFieldNames[
x.field
@@ -633,11 +701,7 @@ export default function ContextEditor({
href={r.href}
>
{r.title}
</Link>{' '}
· {r.kind} · v
{r.version ??
'não exposta'}{' '}
· origem {r.key}
</Link>
</p>
),
)}
@@ -649,14 +713,7 @@ export default function ContextEditor({
</th>
<td className="p-2">
{plan.dependent_results.length}{' '}
resultados financeiros acessíveis
afetados:{' '}
{plan.dependent_results
.map((r) => r.id)
.join(', ') || 'nenhum'}
. Etapas:{' '}
{plan.review_steps.join(', ') ||
'nenhuma alteração de dependências'}
resultados financeiros afectados. Etapas a rever: {plan.review_steps.length}
. Dados já preenchidos nos
formulários conservam-se até à sua
conferência.
@@ -668,12 +725,11 @@ export default function ContextEditor({
</th>
<td className="p-2">
{plan.requests.length} pedidos e os
snapshots, decisões e resultados
dados, decisões e resultados
anteriores.{' '}
{plan.requests.map((r) => (
<p key={r.id}>
{r.protocol || r.id} · v
{r.version} · {r.status}
{r.protocol || 'Pedido guardado'}
</p>
))}
</td>
@@ -706,7 +762,7 @@ export default function ContextEditor({
disabled={busy}
onClick={() => void confirm()}
>
Confirmar e guardar o contexto
Guardar dados do investimento
</button>
</div>
</section>
@@ -717,7 +773,7 @@ export default function ContextEditor({
</h2>
{view.results.map((r) => (
<p key={r.id}>
{r.id} · {r.engine} ·{' '}
Resultado guardado —{' '}
{r.context_state === 'review_required'
? 'Desatualizado para o contexto atual — exige revisão/recálculo'
: 'Guardado para o contexto indicado no dossier'}
@@ -729,7 +785,7 @@ export default function ContextEditor({
className="underline"
href={'/balcao-virtual/pedidos/' + r.id}
>
{r.protocol || r.id}
{r.protocol || 'Consultar pedido'}
</Link>{' '}
· {r.status} ·{' '}
{r.context_state === 'historical_submission'
+2 -2
View File
@@ -36,7 +36,7 @@ export default function InvestmentContexts({
<main className="mx-auto max-w-5xl space-y-6 p-4 md:p-8">
<Head title="Os meus investimentos" />
<h1 className="text-2xl font-semibold">Os meus investimentos</h1>
{zoneSource && <section className="rounded border p-4"><h2>Parque consultado: {zoneSource.title} · versão {zoneSource.consulted_public_version}</h2><p>{zoneSource.notice}</p><Link className="underline" href={zoneSource.href}>Voltar à ficha pública</Link></section>}
{zoneSource && <section className="rounded border p-4"><h2>Parque escolhido: {zoneSource.title}</h2><Link className="underline" href={zoneSource.href}>Consultar parque</Link></section>}
{sectorSource && (
<p>
Preparar uma ideia em{' '}
@@ -53,7 +53,7 @@ export default function InvestmentContexts({
<Link className="underline" href={contentSource.href}>
{contentSource.title}
</Link>
. A fonte e a versão serão guardadas no dossier escolhido.
.
</p>
)}
<p>
+22 -19
View File
@@ -3,6 +3,8 @@ import {
type Preparation,
} from '@/components/private-investment-preparation';
import ContextDocuments from '@/components/context-documents';
import InvestmentRequirements from '@/components/investment-requirements';
import OpportunityChoices from '@/components/investment-opportunity-choices';
import type { DossierContext } from '@/lib/investor-context';
import FintechInvestment, {
type FintechView,
@@ -117,12 +119,9 @@ function Result({ result }: { result: PathResult }) {
</p>
<p className="text-sm">
Estimativa recalculada pelas fórmulas da ferramenta a partir
das entradas guardadas, versão {result.draft_version}. Não
dos dados preenchidos. Não
constitui decisão de financiamento.
</p>
<p className="text-xs break-all">
Evidência {result.id} · {result.engine}
</p>
</li>
);
} catch (error) {
@@ -449,16 +448,14 @@ export default function InvestmentPath(path: Path) {
{path.investment_context && (
<section className="space-y-2 rounded border border-emerald-300 bg-emerald-50 p-4">
<h2 className="font-semibold">
Contexto atual — revisão{' '}
{path.investment_context.revision}
As suas escolhas
</h2>
{Object.values(path.investment_context.selection).map(
(ref) => (
<p key={ref.key}>
<Link className="underline" href={ref.href}>
{ref.title}
</Link>{' '}
· versão {ref.version ?? 'não exposta'}
</Link>
</p>
),
)}
@@ -490,15 +487,9 @@ export default function InvestmentPath(path: Path) {
{zone.title}
</Link>
<p>
UUID {zone.zone_id} · versão consultada{' '}
{zone.consulted_public_version} ·{' '}
{zone.selected_at}
</p>
<p>{zone.notice}</p>
<p>
A ficha atual pode ter uma versão diferente;
esta referência conserva a consulta registada.
</p>
</article>
))}
</section>
@@ -539,6 +530,8 @@ export default function InvestmentPath(path: Path) {
}
/>
)}
{!!path.investment_context?.opportunity_choices?.length && <OpportunityChoices choices={path.investment_context.opportunity_choices}/>}
<InvestmentRequirements assessment={path.investment_context?.requirements_assessment} contextId={path.case.id} excludedSteps={path.steps.filter((step) => !step.preparation_allowed).map((step) => step.code)}/>
{!!path.content_sources?.length && (
<section
className="space-y-2 rounded-xl border p-4"
@@ -576,15 +569,14 @@ export default function InvestmentPath(path: Path) {
<h2 className="font-semibold">Próxima acção</h2>
{path.next ? (
<a className="underline" href={path.next.href}>
{path.next.action}: {path.next.code} · {path.next.title}
{path.next.action}: {path.next.title}
</a>
) : (
<p>Reveja as pendências e o acompanhamento.</p>
)}
<p className="text-sm">
Pode preparar etapas independentes enquanto aguarda
informação. Percurso v{path.definition_version}; os outros
roteiros mantêm a numeração original.
informação. As etapas abaixo adaptam-se às suas escolhas.
</p>
</section>
<nav
@@ -693,14 +685,25 @@ export default function InvestmentPath(path: Path) {
</button>
</form>
)}
{Object.entries(path.phases).map(([number, label]) => (
{path.steps.some((step) => !step.preparation_allowed) && (
<section className="rounded-xl border p-4" aria-label="Etapas sem aplicação">
<h2 className="font-semibold">Não precisa de preparar neste contexto</h2>
<ul>
{path.steps.filter((step) => !step.preparation_allowed).map((step) => (
<li key={step.code}>{step.title}: {step.reason || 'Conforme as escolhas guardadas.'}</li>
))}
</ul>
<p className="text-sm">Pode rever estas escolhas acima. Os dados e o histórico destas etapas ficam conservados.</p>
</section>
)}
{Object.entries(path.phases).filter(([number]) => path.steps.some((step) => step.phase === Number(number) && step.preparation_allowed)).map(([number, label]) => (
<section className="space-y-3" key={number}>
<h2 className="text-lg font-semibold">
Fase {number} · {label}
</h2>
<div className="grid items-start gap-3 md:grid-cols-2">
{path.steps
.filter((step) => step.phase === Number(number))
.filter((step) => step.phase === Number(number) && step.preparation_allowed)
.map((step) => (
<Stage
key={step.code}
@@ -18,6 +18,8 @@ export default function ContentHub({access, counts}: {access: Access; counts: Re
<p>Sector económico e modalidade são escolhas distintas. Pode guardar preparação incompleta; o editor identifica os campos e variantes ainda sem suporte de submissão.</p>
{access.opportunities.create && <Link className={button} href={access.opportunities.create}>Nova oportunidade</Link>}
<Link className="block underline" href={access.opportunities.index}>Gerir oportunidades e histórico</Link>
{access.opportunities.profileWorkflow && <Link className="block underline" href="/minhas-oportunidades/lotes/agronegocio">Rever o lote das 34 oportunidades de Agronegócio</Link>}
<p>Para divulgar um resumo ainda em preparação, abra a ficha e escolha <strong>Divulgação promocional</strong>: prepare o texto público, reveja a prévia e confirme divulgar, actualizar ou retirar. Esta operação não conclui a validação do investimento.</p>
<ul>{Object.entries(counts.opportunities ?? {}).map(([state, total]) => <li key={state}><Link className="underline" href={`${access.opportunities!.index}?status=${state}`}>{states[state] ?? state}: {total}</Link></li>)}</ul>
<p className="text-sm">As etapas e a independência já definidas para oportunidades mantêm-se. Esta entrada não atribui autorização para rever ou publicar.</p>
{!access.opportunities.profileWorkflow && <p>O seu acesso actual abre o cadastro legado de rascunhos. A submissão, revisão e publicação de novas oportunidades utilizam os perfis operacionais já existentes; a administração de conteúdos não substitui essa designação. Esta entrada não apresenta o rascunho legado como percurso completo de publicação.</p>}
@@ -0,0 +1,25 @@
import { Link, router } from '@inertiajs/react';
import { useState } from 'react';
import { profileJson } from '@/lib/profile-json';
import { createOperationKey } from '@/lib/operation-key';
import { Management, button, control } from './shared';
type Entry = {key:string;id:number|null;title:string;summary:string;description:string;corridor:string;status:string|null;href:string|null};
type Preview = {id:number;token:string;projection:{title:string;summary:string;description:string};operation_key:string};
export default function AgribusinessBatch({entries,manifestHash,canRegister}:{entries:Entry[];manifestHash:string;canRegister:boolean}) {
const [previews,setPreviews]=useState<Preview[]>([]); const [reason,setReason]=useState(''); const [confirmed,setConfirmed]=useState(false); const [busy,setBusy]=useState(false); const [message,setMessage]=useState('');
const base='/minhas-oportunidades/lotes/agronegocio';
async function run(action:'register'|'preview'|'disclose') {setBusy(true);setMessage('');try {
if(action==='register'){await profileJson(base+'/cadastrar',{manifest_hash:manifestHash,confirm:true});router.reload();}
if(action==='preview'){const result=await profileJson(base+'/previa',{}) as {previews:Preview[]};setPreviews(result.previews.map(p=>({...p,operation_key:createOperationKey()})));setConfirmed(false);}
if(action==='disclose'){await profileJson(base+'/divulgar',{previews:previews.map(({id,token,operation_key})=>({id,token,operation_key})),reason,confirm:confirmed});setPreviews([]);setMessage('As 34 fichas foram divulgadas como Em preparação; a execução não foi autorizada.');router.reload();}
}catch(e){setMessage(e instanceof Error?e.message:'Operação não concluída. A versão anterior foi preservada.');}finally{setBusy(false);}}
return <Management title="Oportunidades de Agronegócio"><p>Reveja as oportunidades, complete a informação disponível e confira a prévia antes de divulgar.</p>
<p>{entries.filter(e=>e.id).length} cadastradas · {entries.filter(e=>e.status==='published').length} divulgadas</p>
{canRegister && <button className={button} disabled={busy} onClick={()=>void run('register')}>Cadastrar ou recuperar as 34 fichas privadamente</button>}
{entries.every(e=>e.id) && <button className={button+' ml-3'} disabled={busy} onClick={()=>void run('preview')}>Pré-visualizar divulgação das 34 fichas</button>}
<ul className="my-5 space-y-3">{entries.map(e=><li key={e.key} className="rounded border p-3"><h2>{e.title}</h2><p>{e.summary}</p><p>{e.corridor} · {e.status??'Por cadastrar'}</p>{e.id&&<Link className="underline" href={`/minhas-oportunidades/${e.id}/edit`}>Rever e editar a ficha</Link>}</li>)}</ul>
{!!previews.length && <section aria-label="Prévia pública das 34 fichas"><h2>Conteúdo a divulgar — Em preparação</h2>{previews.map(p=><details key={p.id} className="my-3 rounded border p-3"><summary>{p.projection.title}</summary><p>{p.projection.summary}</p><p className="whitespace-pre-line">{p.projection.description}</p></details>)}<label className="block">Motivo<textarea className={control} value={reason} onChange={e=>setReason(e.target.value)}/></label><label className="block"><input type="checkbox" checked={confirmed} onChange={e=>setConfirmed(e.target.checked)}/>Confirmo a divulgação destes resumos em preparação; não é autorização de execução.</label><button className={button} disabled={busy||!confirmed||reason.trim().length<20} onClick={()=>void run('disclose')}>Divulgar as 34 oportunidades no catálogo</button></section>}
{message&&<p role="status">{message}</p>}<Link className="block underline" href="/oportunidades?sector=agronegocio">Consultar catálogo de Agronegócio</Link>
</Management>;
}
@@ -17,6 +17,7 @@ type Entry = {
updated_at: string;
deleted_at: string | null;
can_edit?: boolean;
can_promote?: boolean;
working_status?: string;
draft_title?: string;
publication?: { scope: string; label: string; public_catalog: boolean };
@@ -232,6 +233,7 @@ export default function Opportunities({
<p>Atualizado: {r.updated_at}</p>
<div className="flex flex-wrap gap-4">
<Link href={`${basePath}/${r.id}/view`}>Ver</Link>
{r.can_promote && <Link href={`/minhas-oportunidades/${r.id}/divulgacao`}>Divulgação promocional</Link>}
{r.can_edit !== false && (
<Link href={`${basePath}/${r.id}/edit`}>
{r.deleted_at || r.status === 'archived'
@@ -75,9 +75,11 @@ export default function OpportunityDraft({
workflowParticipants,
publicationPreview,
publicationState,
promotionHref,
auth,
canEditDraft = false,
} = usePage<{
promotionHref?: string | null;
basePath?: string;
profileMode?: boolean;
profileOrganization?: string | null;
@@ -246,6 +248,15 @@ export default function OpportunityDraft({
<Link href={basePath}>
{profileMode ? 'Minhas oportunidades' : 'Voltar à gestão'}
</Link>
{promotionHref && <Link className="ml-3 underline" href={promotionHref}>Divulgação promocional — Em preparação</Link>}
{!!profileContext?.corridor_agribusiness && (
<section className="my-4 space-y-2 rounded border p-4">
<h2 className="font-semibold">Origem e enquadramento da oportunidade</h2>
<p>{valueText((profileContext.corridor_agribusiness as Record<string, unknown>).corridor)}</p>
<p>Fonte: {valueText((profileContext.corridor_agribusiness as Record<string, unknown>).source)}</p>
<p>Em preparação. O corredor indicado na fonte não confirma implantação, promotor, viabilidade ou financiamento. Complete a ficha e reveja a prévia antes de divulgar.</p>
</section>
)}
{record && (
<Link
href={`/investimentos/iniciar?source=opportunity:${record.id}`}
@@ -0,0 +1,77 @@
import { Link, router } from '@inertiajs/react';
import { useState } from 'react';
import { createOperationKey } from '@/lib/operation-key';
import { profileJson, ProfileValidationError } from '@/lib/profile-json';
import { Management, Field, control, button } from './shared';
type Draft = {title: string; summary: string; description: string; limitations: string; source_reference: string; edition: string; source_date: string; sector_id: number | null; territory_ids: string[]};
type Projection = {title: string; summary: string; description: string; sector_id: number | null; maturity_label: string; territories: {id: string; label: string}[]};
type Preview = {token: string; action: 'disclose' | 'withdraw'; projection: Projection; current: {title: string; summary: string; description: string; status: string}};
export default function OpportunityPromotion({record, initial, etag, publicNow, reauthenticationRequired, sectors, territories, history}: {
record: {id: number; reference: string}; initial: Draft; etag: string; publicNow: boolean; reauthenticationRequired: boolean;
sectors: {id: number; name: string}[]; territories: {id: string; name: string; location_type: string}[];
history: {id: string; action: string; actor_user_id: number; occurred_at: string}[];
}) {
const [draft, setDraft] = useState<Draft>({...initial, source_date: initial.source_date ?? ''});
const [preview, setPreview] = useState<Preview | null>(null);
const [operationKey, setOperationKey] = useState(createOperationKey);
const [reason, setReason] = useState('');
const [confirmed, setConfirmed] = useState(false);
const [busy, setBusy] = useState(false);
const [message, setMessage] = useState('');
const [errors, setErrors] = useState<Record<string,string>>({});
const [needsMfa, setNeedsMfa] = useState(reauthenticationRequired);
function change<K extends keyof Draft>(key: K, value: Draft[K]) { setDraft(current => ({...current, [key]:value})); setPreview(null); setConfirmed(false); }
function report(error: unknown) {setMessage(error instanceof Error ? error.message : 'Não foi possível concluir. A ficha anterior foi preservada.'); setErrors(error instanceof ProfileValidationError ? error.fields : {}); if(error instanceof ProfileValidationError && error.reauthenticate)setNeedsMfa(true);}
function renewAuthentication() { router.post('/logout', {}, {onSuccess:()=>router.visit(`/minhas-oportunidades/${record.id}/divulgacao`)}); }
async function prepare(action: 'disclose' | 'withdraw') {
setBusy(true); setMessage(''); setErrors({}); setPreview(null); setConfirmed(false);
try { const result = await profileJson(`/minhas-oportunidades/${record.id}/divulgacao/previa`, {...draft, etag, action}); setPreview(result); setOperationKey(createOperationKey()); }
catch (error) { report(error); } finally { setBusy(false); }
}
async function publish() {
if (!preview || !confirmed) return;
setBusy(true); setErrors({});
try {const result = await profileJson(`/minhas-oportunidades/${record.id}/divulgacao/confirmar`, {token:preview.token, operation_key:operationKey, reason, confirm:confirmed});
setMessage(result.message); setPreview(null); setConfirmed(false); router.reload();
} catch (error) { report(error); } finally {setBusy(false);}
}
const labels: Record<string,string> = {promotional_summary_published:'Resumo divulgado', promotional_summary_updated:'Resumo actualizado', promotional_summary_withdrawn:'Resumo retirado'};
return <Management title={`Divulgação promocional — ${record.reference}`}>
<Link className="underline" href={`/minhas-oportunidades/${record.id}/view`}>Voltar à oportunidade</Link>
<p>Prepare o resumo público e reveja a prévia antes de confirmar. A divulgação fica identificada como <strong>Em preparação</strong> e não autoriza a execução do investimento. Documentos e contactos do cadastro continuam privados.</p>
<p role="status">{publicNow ? 'Resumo actualmente divulgado.' : 'Resumo não divulgado.'} {message}</p>
{needsMfa && <p role="alert">Entre novamente com a sua conta e MFA para confirmar esta operação. O cadastro guardado permanece disponível; a prévia ainda não confirmada terá de ser preparada novamente. <button className="underline" onClick={renewAuthentication}>Renovar entrada com MFA</button></p>}
{Object.entries(errors).map(([key,value]) => <p role="alert" key={key}>{key}: {value}</p>)}
<form className="space-y-4 rounded border p-5" onSubmit={e=>{e.preventDefault(); void prepare('disclose');}}>
<Field label="Título público"><input required className={control} maxLength={255} value={draft.title} onChange={e=>change('title',e.target.value)}/></Field>
<Field label="Resumo público"><textarea required className={control} maxLength={499} value={draft.summary} onChange={e=>change('summary',e.target.value)}/></Field>
<Field label="Descrição pública"><textarea className={control} maxLength={6000} value={draft.description} onChange={e=>change('description',e.target.value)}/></Field>
<Field label="Limitações e lacunas conhecidas"><textarea className={control} value={draft.limitations} onChange={e=>change('limitations',e.target.value)}/></Field>
<Field label="Fonte do resumo"><textarea className={control} value={draft.source_reference} onChange={e=>change('source_reference',e.target.value)}/></Field>
<Field label="Edição"><input className={control} value={draft.edition} onChange={e=>change('edition',e.target.value)}/></Field>
<Field label="Data-base"><input className={control} type="date" value={draft.source_date} onChange={e=>change('source_date',e.target.value)}/></Field>
<Field label="Sector divulgado"><select className={control} value={draft.sector_id ?? ''} onChange={e=>change('sector_id',e.target.value ? Number(e.target.value):null)}><option value="">Por indicar</option>{sectors.map(s=><option key={s.id} value={s.id}>{s.name}</option>)}</select></Field>
<Field label="Acrescentar abrangência territorial documentada"><select className={control} value="" onChange={e=>{if(e.target.value && !draft.territory_ids.includes(e.target.value))change('territory_ids',[...draft.territory_ids,e.target.value]);}}><option value="">Seleccionar território existente</option>{territories.map(t=><option key={t.id} value={t.id}>{t.name} · {t.location_type}</option>)}</select></Field>
<p>Seleccione apenas a abrangência que pretende divulgar e cuja origem está identificada. Abrangência não comprova implantação ou disponibilidade de terreno. Sem selecção, a localização fica por indicar.</p>
<ul>{draft.territory_ids.map(id=><li key={id}>{territories.find(t=>t.id===id)?.name ?? 'Referência anterior indisponível'} <button type="button" className="underline" onClick={()=>change('territory_ids',draft.territory_ids.filter(t=>t!==id))}>Remover abrangência</button></li>)}</ul>
<button className={button} disabled={busy || needsMfa}>{publicNow ? 'Rever actualização do resumo' : 'Preparar prévia de divulgação'}</button>
{publicNow && <button type="button" className={`${button} ml-3`} disabled={busy || needsMfa} onClick={()=>void prepare('withdraw')}>Preparar retirada do resumo</button>}
</form>
{preview && <section className="space-y-4 rounded border p-5" aria-label="Prévia da divulgação">
<h2 className="text-xl font-semibold">{preview.action === 'withdraw' ? 'Rever retirada do catálogo' : 'Prévia do conteúdo público'}</h2>
<p><strong>{preview.projection.maturity_label}</strong></p>
<h3>{preview.projection.title}</h3><p>{preview.projection.summary}</p><p className="whitespace-pre-wrap">{preview.projection.description}</p>
<p>Sector: {sectors.find(s=>s.id===preview.projection.sector_id)?.name ?? 'Por indicar'}</p>
<ul>{preview.projection.territories.map(t=><li key={t.id}>{t.label}</li>)}</ul>
{!preview.projection.territories.length && <p>Abrangência territorial por indicar.</p>}
<p>Esta prévia ainda não alterou o portal público. {preview.action === 'withdraw' ? 'A confirmação retira o resumo da consulta pública e preserva o cadastro.' : 'Só este texto e o enquadramento apresentado serão divulgados; os anexos e contactos não são incluídos.'}</p>
<Field label="Motivo da divulgação, alteração ou retirada"><textarea required className={control} minLength={10} maxLength={1000} value={reason} onChange={e=>setReason(e.target.value)}/></Field>
<label className="block"><input type="checkbox" checked={confirmed} onChange={e=>setConfirmed(e.target.checked)}/> Confirmo o conteúdo público desta prévia e a operação indicada.</label>
<button className={button} disabled={busy || !confirmed || reason.trim().length<10} onClick={()=>void publish()}>{preview.action === 'withdraw' ? 'Confirmar retirada do resumo' : 'Confirmar divulgação do resumo'}</button>
<button className="ml-3 underline" disabled={busy} onClick={()=>{setPreview(null);setConfirmed(false);}}>Cancelar prévia</button>
</section>}
<section aria-label="Histórico da divulgação"><h2 className="text-xl font-semibold">Histórico da divulgação</h2><ul>{history.map(h=><li key={h.id}>{labels[h.action] ?? h.action} · {h.occurred_at} · responsável {h.actor_user_id}</li>)}</ul></section>
</Management>;
}
+1
View File
@@ -105,6 +105,7 @@ export default function LegacySector({
? article.group.heading
: article.title}
</h1>
{article.sector?.slug === 'agronegocio' && <Link className="inline-flex rounded-lg bg-emerald-800 px-5 py-3 font-semibold text-white" href="/oportunidades?sector=agronegocio&view=corridors">Explorar oportunidades por corredor</Link>}
<p>
Explore o setor e prepare a sua ideia com o contexto
guardado no dossier.
+2
View File
@@ -13,8 +13,10 @@ Route::middleware(['auth', 'verified', PrivateInvestmentResponse::class, Virtual
Route::get('/contexto-atual', [InvestorContextController::class, 'current']);
Route::put('/contexto-atual', [InvestorContextController::class, 'focus']);
Route::get('/fontes-contexto', [InvestorContextController::class, 'options']);
Route::get('/opcoes-corredor', [InvestorContextController::class, 'corridorOptions']);
Route::get('/{id}/contexto', [InvestorContextController::class, 'show'])->whereUuid('id');
Route::get('/{id}/contexto/editar', [InvestorContextController::class, 'edit'])->whereUuid('id');
Route::post('/{id}/contexto/rascunho', [InvestorContextController::class, 'saveDraft'])->whereUuid('id');
Route::post('/{id}/contexto/previa', [InvestorContextController::class, 'preview'])->whereUuid('id');
Route::post('/{id}/contexto/confirmar', [InvestorContextController::class, 'commit'])->whereUuid('id');
Route::get('/a-partir-do-parque/{zoneId}', [Path::class, 'zoneStart'])->whereUuid('zoneId');
+7
View File
@@ -166,6 +166,10 @@ Route::middleware(['auth', 'verified', EnsurePortalProfileAdministration::class]
Route::middleware(['auth', 'verified', EnsurePortalProfiles::class])->group(function () {
$profiles = PortalProfileController::class;
Route::get('/minhas-oportunidades', [$profiles, 'opportunities']);
Route::get('/minhas-oportunidades/lotes/agronegocio', [\App\Http\Controllers\CorridorAgribusinessController::class, 'show']);
Route::post('/minhas-oportunidades/lotes/agronegocio/cadastrar', [\App\Http\Controllers\CorridorAgribusinessController::class, 'register']);
Route::post('/minhas-oportunidades/lotes/agronegocio/previa', [\App\Http\Controllers\CorridorAgribusinessController::class, 'preview']);
Route::post('/minhas-oportunidades/lotes/agronegocio/divulgar', [\App\Http\Controllers\CorridorAgribusinessController::class, 'disclose']);
Route::get('/minhas-oportunidades/territory-options', [InvestmentTerritoryController::class, 'options']);
Route::get('/minhas-oportunidades/territory-geometries', [InvestmentTerritoryController::class, 'geometries']);
Route::get('/minhas-oportunidades/create', [$profiles, 'edit']);
@@ -177,6 +181,9 @@ Route::middleware(['auth', 'verified', EnsurePortalProfiles::class])->group(func
Route::get('/minhas-oportunidades/{id}/classification', [OpportunityClassificationController::class, 'index'])->whereNumber('id');
Route::post('/minhas-oportunidades/{id}/classification/review', [OpportunityClassificationController::class, 'review'])->whereNumber('id');
Route::post('/minhas-oportunidades/{id}/workflow', [$profiles, 'workflow'])->whereNumber('id');
Route::get('/minhas-oportunidades/{id}/divulgacao', [\App\Http\Controllers\OpportunityPromotionController::class, 'show'])->whereNumber('id');
Route::post('/minhas-oportunidades/{id}/divulgacao/previa', [\App\Http\Controllers\OpportunityPromotionController::class, 'preview'])->whereNumber('id');
Route::post('/minhas-oportunidades/{id}/divulgacao/confirmar', [\App\Http\Controllers\OpportunityPromotionController::class, 'confirm'])->whereNumber('id');
Route::post('/minhas-oportunidades/{id}/contacts', [$profiles, 'contact'])->whereNumber('id');
Route::get('/minhas-oportunidades/{id}/resources', [OpportunityResourceController::class, 'index'])->whereNumber('id');
Route::post('/minhas-oportunidades/{id}/library-support', [OpportunityResourceController::class, 'reuseLibrary'])->whereNumber('id');
@@ -0,0 +1,47 @@
<?php
namespace Tests\Feature;
use App\Models\Opportunity;
use App\Models\User;
use App\Services\CorridorAgribusinessOpportunities;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Tests\TestCase;
final class CorridorAgribusinessBatchTest extends TestCase
{
use DatabaseTransactions;
public function test_batch_uses_normal_scoped_session_preview_and_idempotent_disclosure_without_touching_other_opportunities(): void
{
$target = DB::selectOne('select current_database() as name, inet_server_port() as port');
$this->assertSame('simulator_fiscal_delta_20261021_test', $target->name);
$this->assertSame(55441, (int) $target->port);
$this->withoutVite(); config(['portal.profile_access_enabled' => true]);
$entries = app(CorridorAgribusinessOpportunities::class)->entries();
$keys = array_column($entries, 'key');
$others = Opportunity::withTrashed()->whereNotIn('management_creation_key', $keys)->orWhereNull('management_creation_key')->orderBy('id')->get()->map(fn ($r) => $r->getRawOriginal())->all();
$count = Opportunity::withTrashed()->count();
$actor = User::findOrFail(295);
$this->actingAs($actor)->withSession(['portal.mfa_actor' => $actor->id, 'portal.mfa_at' => time(), 'portal.access_revision' => (int) $actor->portal_access_revision]);
$base = '/minhas-oportunidades/lotes/agronegocio';
$this->get($base)->assertOk();
$this->postJson($base.'/cadastrar', ['manifest_hash' => str_repeat('0', 64), 'confirm' => true])->assertConflict();
$this->postJson($base.'/cadastrar', ['manifest_hash' => hash('sha256', json_encode($entries, JSON_THROW_ON_ERROR)), 'confirm' => true])->assertOk();
$this->assertSame($count, Opportunity::withTrashed()->count());
$previews = $this->postJson($base.'/previa')->assertOk()->json('previews');
$this->assertCount(34, $previews);
$input = ['confirm' => true, 'reason' => 'QA: divulgação das fichas expressamente seleccionadas no lote de Agronegócio.',
'previews' => array_map(fn ($p) => ['id' => $p['id'], 'token' => $p['token'], 'operation_key' => (string) Str::uuid()], $previews)];
$bad = $input; array_pop($bad['previews']);
$this->postJson($base.'/divulgar', $bad)->assertUnprocessable();
$this->postJson($base.'/divulgar', $input)->assertOk();
$this->postJson($base.'/divulgar', $input)->assertOk();
$this->assertSame(34, Opportunity::whereIn('management_creation_key', $keys)->published()->count());
$this->assertSame($others, Opportunity::withTrashed()->whereNotIn('management_creation_key', $keys)->orWhereNull('management_creation_key')->orderBy('id')->get()->map(fn ($r) => $r->getRawOriginal())->all());
$this->withSession(['portal.mfa_at' => 0])->postJson($base.'/previa')->assertForbidden();
$this->actingAs(User::factory()->create())->get($base)->assertForbidden();
}
}
@@ -0,0 +1,57 @@
<?php
namespace Tests\Feature;
use App\Models\Opportunity;
use App\Models\User;
use App\Services\CorridorAgribusinessOpportunities;
use App\Services\OpportunityManagement;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Tests\TestCase;
final class CorridorAgribusinessInvestorPathTest extends TestCase
{
use DatabaseTransactions;
public function test_interested_investor_recovers_the_opportunity_in_the_same_individual_path_and_interest_draft(): void
{
$target = DB::selectOne('select current_database() as name, inet_server_port() as port');
$this->assertSame('simulator_fiscal_delta_20261021_test', $target->name);
$this->assertSame(55441, (int) $target->port);
$this->withoutVite();
config(['portal.profile_access_enabled' => true, 'portal.opportunity_specialized_drafts_enabled' => true,
'investor-path.enabled' => true, 'suitecrm.enabled' => false, 'investmoz_ai.enabled' => false]);
$actor = User::findOrFail(295);
$this->actingAs($actor)->withSession(['portal.mfa_actor' => $actor->id, 'portal.mfa_at' => time(), 'portal.access_revision' => (int) $actor->portal_access_revision]);
$entry = app(CorridorAgribusinessOpportunities::class)->entries()[0];
$record = Opportunity::where('management_creation_key', $entry['key'])->firstOrFail();
$base = '/minhas-oportunidades/'.$record->id.'/divulgacao';
$preview = $this->postJson($base.'/previa', [...$record->workflow_context['promotional_summary']['input'],
'action' => 'disclose', 'etag' => app(OpportunityManagement::class)->etag($record)])->assertOk()->json();
$this->postJson($base.'/confirmar', ['token' => $preview['token'], 'operation_key' => (string) Str::uuid(), 'confirm' => true,
'reason' => 'QA: conferir o roteiro de um interessado sem atribuir qualquer autorização.'])->assertOk();
$investor = User::factory()->create();
$this->actingAs($investor);
$this->get('/oportunidades/'.$record->slug)->assertOk();
$id = $this->postJson('/investimentos', ['title' => 'QA: investimento a partir de agronegócio', 'client_key' => (string) Str::uuid(), 'reuse_consent' => true])->assertCreated()->json('id');
$view = $this->getJson('/investimentos/'.$id.'/contexto')->assertOk()->json();
$body = ['version' => $view['case']['version'], 'selection' => ['opportunity' => 'opportunity:'.$record->id]];
$plan = $this->postJson('/investimentos/'.$id.'/contexto/previa', $body)->assertOk()->json();
$this->postJson('/investimentos/'.$id.'/contexto/confirmar', [...$body, 'preview_hash' => $plan['preview_hash'],
'operation_key' => (string) Str::uuid(), 'confirm' => true])->assertOk();
$this->getJson('/investimentos/'.$id.'/contexto')->assertOk()
->assertJsonPath('context.selection.opportunity.key', 'opportunity:'.$record->id)
->assertJsonPath('context.fields.project_title', 'QA: investimento a partir de agronegócio')
->assertJsonPath('context.fields.project_summary', $record->summary)
->assertJsonPath('context.fields.sector_id', '1');
$this->get('/investimentos/'.$id)->assertOk();
$interestId = $this->postJson('/investidor/manifestacoes', ['opportunity_id' => $record->id])->assertCreated()->json('id');
$this->getJson('/investidor/manifestacoes/'.$interestId)->assertOk();
$this->assertSame(1, DB::table('desk_cases')->where('id', $id)->count());
$this->assertSame('opportunity:'.$record->id, app(\App\Services\InvestorContext::class)->view($investor, $id)['context']['selection']['opportunity']['key']);
// All synthetic investor/path/disclosure writes roll back with this test.
}
}
@@ -0,0 +1,92 @@
<?php
namespace Tests\Feature;
use App\Models\Opportunity;
use App\Models\User;
use App\Services\CorridorAgribusinessOpportunities;
use App\Services\OpportunityManagement;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Tests\TestCase;
final class CorridorAgribusinessOpportunitiesTest extends TestCase
{
use DatabaseTransactions;
protected function setUp(): void
{
parent::setUp();
$target = DB::selectOne('select current_database() as name, inet_server_port() as port');
$this->assertSame('simulator_fiscal_delta_20261021_test', $target->name);
$this->assertSame(55441, (int) $target->port);
$this->withoutVite();
config(['portal.profile_access_enabled' => true, 'portal.opportunity_specialized_drafts_enabled' => true, 'investor-path.enabled' => true]);
$actor = User::findOrFail(295); // Existing designated QA operator; no new role or account.
$this->actingAs($actor)->withSession(['portal.mfa_actor' => $actor->id, 'portal.mfa_at' => time(), 'portal.access_revision' => (int) $actor->portal_access_revision]);
}
public function test_all_34_records_have_editable_drafts_and_individual_disclosure_and_path(): void
{
$service = app(CorridorAgribusinessOpportunities::class);
$entries = $service->entries();
$this->assertCount(34, $entries);
$masters = DB::table('development_corridors')->orderBy('id')->get()->toJson();
$this->assertSame([], $service->articleLinks());
foreach ($entries as $entry) {
$record = Opportunity::where('management_creation_key', $entry['key'])->firstOrFail();
$this->assertSame('draft', $record->status);
$this->assertNull($record->primary_territory_id);
$this->assertNull($record->promoter_name);
$this->assertNull($record->estimated_investment_amount);
$this->assertSame($entry['corridor'], $record->workflow_context['corridor_agribusiness']['corridor']);
$base = '/minhas-oportunidades/'.$record->id;
$this->get($base.'/edit')->assertOk();
$this->get($base.'/divulgacao')->assertOk();
$input = $record->workflow_context['promotional_summary']['input'];
$preview = $this->postJson($base.'/divulgacao/previa', [...$input, 'action' => 'disclose', 'etag' => app(OpportunityManagement::class)->etag($record)])->assertOk()->json();
$this->assertStringContainsString($entry['corridor'], $preview['projection']['description']);
$this->postJson($base.'/divulgacao/confirmar', ['token' => $preview['token'], 'operation_key' => (string) Str::uuid(), 'confirm' => true,
'reason' => 'QA: conferir divulgação individual da fonte e ligação ao roteiro.'])->assertOk();
$this->getJson('/api/v1/opportunities/'.$record->slug)->assertOk()->assertJsonPath('data.maturity_label', 'Em preparação');
$this->get('/oportunidades/'.$record->slug)->assertOk();
$this->get('/investimentos/iniciar?source=opportunity:'.$record->id)->assertOk();
$this->assertSame($record->id, (int) app(\App\Services\InvestorContext::class)->reference('opportunity:'.$record->id)['id']);
}
$links = $service->articleLinks();
$this->assertSame([5, 6, 6, 7, 5, 5], array_map('count', $links));
$this->assertSame($masters, DB::table('development_corridors')->orderBy('id')->get()->toJson());
// DatabaseTransactions rolls back these QA disclosures; the 34 real-source drafts stay private.
}
public function test_edit_recover_preview_update_withdraw_and_idempotent_loading_preserve_editorial_work(): void
{
$record = Opportunity::where('management_creation_key', app(CorridorAgribusinessOpportunities::class)->entries()[0]['key'])->firstOrFail();
$base = '/minhas-oportunidades/'.$record->id;
$canonical = app(OpportunityManagement::class)->values($record)['canonical'];
$canonical['executive_summary'] .= ' Correcção editorial QA.';
$this->putJson($base.'/draft', ['etag' => app(OpportunityManagement::class)->etag($record), 'canonical' => $canonical,
'reason' => 'QA: corrigir e recuperar os dados através da gestão.'])->assertOk();
$this->assertSame($canonical['executive_summary'], app(OpportunityManagement::class)->values($record->fresh())['canonical']['executive_summary']);
$this->get($base.'/edit')->assertOk();
$input = $record->fresh()->workflow_context['promotional_summary']['input'];
foreach (['disclose', 'disclose', 'withdraw'] as $index => $action) {
$record->refresh();
if ($index === 1) $input['summary'] .= ' Actualização pública QA.';
$preview = $this->postJson($base.'/divulgacao/previa', [...$input, 'action' => $action, 'etag' => app(OpportunityManagement::class)->etag($record)])->assertOk()->json();
$this->postJson($base.'/divulgacao/confirmar', ['token' => $preview['token'], 'operation_key' => (string) Str::uuid(), 'confirm' => true,
'reason' => 'QA: conferir actualização ou retirada sem perder a preparação.'])->assertOk();
}
$this->getJson('/api/v1/opportunities/'.$record->slug)->assertNotFound();
$this->assertSame([], app(CorridorAgribusinessOpportunities::class)->articleLinks());
$this->assertSame($canonical['executive_summary'], app(OpportunityManagement::class)->values($record->fresh())['canonical']['executive_summary']);
$request = \Illuminate\Http\Request::create('/minhas-oportunidades/drafts', 'POST');
app()->instance('request', $request);
$repeat = app(CorridorAgribusinessOpportunities::class)->register(User::findOrFail(295), $record->organization_id);
$this->assertCount(34, $repeat);
$this->assertSame(0, count(array_filter($repeat, fn ($r) => $r['created'])));
$this->assertSame('suspended', $record->fresh()->status);
$this->assertSame($canonical['executive_summary'], app(OpportunityManagement::class)->values($record->fresh())['canonical']['executive_summary']);
}
}
@@ -0,0 +1,187 @@
<?php
namespace Tests\Feature;
use App\Models\Opportunity;
use App\Models\User;
use App\Services\CorridorAgribusinessOpportunities;
use App\Services\InvestmentCorridorContext;
use App\Services\InvestmentOpportunityChoices;
use App\Services\VirtualDesk;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Tests\TestCase;
class InvestmentCorridorContextTest extends TestCase
{
use DatabaseTransactions;
private User $investor;
protected function setUp(): void
{
parent::setUp(); $this->withoutVite();
$t=DB::selectOne('select current_database() as name,inet_server_port() as port');
$this->assertSame('simulator_fiscal_delta_20261021_test',$t->name);$this->assertSame(55441,(int)$t->port);
config(['investor-path.enabled'=>true,'investor-path.private_preparation'=>true,'suitecrm.enabled'=>false,'investmoz_ai.enabled'=>false]);
$this->investor=User::factory()->create();$this->actingAs($this->investor);
}
private function record(string $corridor): Opportunity
{
$entry=collect(app(CorridorAgribusinessOpportunities::class)->entries())->firstWhere('corridor',config('investment_corridors.'.$corridor.'.name'));
return Opportunity::where('management_creation_key',$entry['key'])->firstOrFail();
}
public function test_land_status_uses_the_real_decision_preserves_inactive_values_and_allows_incomplete_drafts(): void
{
$id=$this->postJson('/investimentos',['title'=>'QA situação do terreno','client_key'=>(string)Str::uuid(),'reuse_consent'=>true])->assertCreated()->json('id');
$setNeed=function(string $need) use($id): array {
$version=$this->getJson('/investimentos/'.$id.'/contexto')->json('case.version');
$this->postJson('/investimentos/'.$id.'/aplicabilidade',['version'=>$version,'land_needed'=>$need,'external_funding'=>'unknown','viability'=>'unknown','reason'=>'Cenário QA '.$need])->assertOk();
return $this->getJson('/investimentos/'.$id.'/contexto')->assertOk()->json();
};
$view=$setNeed('yes');
$body=['context'=>$id,'personal_snapshot'=>[],'economic_snapshot'=>['purpose'=>'simulate_only','context_applicability_snapshot'=>$view['prefill']['simulator']['context_applicability_snapshot'],'land_status'=>'duat','selection_labels'=>['land_status'=>'DUAT']]];
$created=$this->postJson('/api/v1/simulation-drafts',$body,['Idempotency-Key'=>(string)Str::uuid()])->assertCreated();
$draft=$created->json('data.id');$etag=$created->headers->get('ETag');
foreach(['no','unknown'] as $need){
$view=$setNeed($need);
$this->assertSame($need,json_decode($view['prefill']['simulator']['context_applicability_snapshot'],true)['land_preparation']['need']);
$update=$this->putJson('/api/v1/simulation-drafts/'.$draft,['personal_snapshot'=>[],'economic_snapshot'=>['purpose'=>'simulate_only','context_applicability_snapshot'=>$body['economic_snapshot']['context_applicability_snapshot']]],['If-Match'=>$etag])->assertOk();
$recovered=$this->getJson('/api/v1/simulation-drafts/'.$draft.'?context='.$id)->assertOk();
// Omitting context cannot bypass the real dossier decision. Hidden values are retained.
$this->assertSame('duat',$recovered->json('data.economic_snapshot.land_status'));
$this->assertSame('DUAT',$recovered->json('data.economic_snapshot.selection_labels.land_status'));
$this->assertSame($need,json_decode($recovered->json('data.economic_snapshot.context_applicability_snapshot'),true)['land_preparation']['need']);
$etag=$update->headers->get('ETag');
}
$setNeed('yes');
$this->flushSession();$this->actingAs($this->investor);
$this->getJson('/api/v1/simulation-drafts/'.$draft.'?context='.$id)->assertOk()->assertJsonPath('data.economic_snapshot.land_status','duat');
$updated=$this->putJson('/api/v1/simulation-drafts/'.$draft,[...$body,'economic_snapshot'=>['purpose'=>'simulate_only','land_status'=>'leased']],['If-Match'=>$etag])->assertOk();
$recovered=$this->getJson('/api/v1/simulation-drafts/'.$draft.'?context='.$id)->assertOk()->assertJsonPath('data.economic_snapshot.land_status','leased');
$this->assertSame('yes',json_decode($recovered->json('data.economic_snapshot.context_applicability_snapshot'),true)['land_preparation']['need']);
$this->putJson('/api/v1/simulation-drafts/'.$draft,[...$body,'economic_snapshot'=>['purpose'=>'simulate_only','land_status'=>'invalid']],['If-Match'=>$updated->headers->get('ETag')])->assertUnprocessable();
$empty=app(\App\Services\InvestorContext::class)->prefill(['fields'=>['site_tenure'=>'lease'],'revision'=>0,'provenance'=>[],'land_preparation'=>['need'=>'yes','reason'=>'QA']]);
$this->assertSame('leased',$empty['simulator']['land_status']);
}
public function test_simulator_reuses_names_for_the_exact_chosen_territory_and_sector_without_creating_a_location(): void
{
$id=$this->postJson('/investimentos',['title'=>'QA continuidade simulador','client_key'=>(string)Str::uuid(),'reuse_consent'=>true])->assertCreated()->json('id');
$province=collect(app(InvestmentCorridorContext::class)->provinces('beira'))->firstWhere('source_code','07')->id;
$district=app(\App\Services\InvestmentGeography::class)->publicReferences()->where('parent_id',$province)->where('location_type','district')->firstOrFail();
$record=$this->record('beira');
$plan=$this->postJson('/investimentos/'.$id.'/contexto/previa',['version'=>$this->getJson('/investimentos/'.$id.'/contexto')->json('case.version'),'selection'=>[],'opportunity_choices'=>[['source'=>'opportunity:'.$record->id,'corridor_key'=>'beira','territory'=>'territory:'.$district->id]]])->assertOk()->json();
$sim=app(\App\Services\InvestorContext::class)->prefill($plan)['simulator'];
$this->assertSame('investor',$sim['journey_type']);
$this->assertSame('simulate_only',$sim['purpose']);
$this->assertSame($province,$sim['province_id']);
$this->assertSame('Sofala',$sim['province_id_label']);
$this->assertSame($district->id,$sim['district_id']);
$this->assertSame($district->name,$sim['district_id_label']);
$this->assertSame((string)$record->sector_id,$sim['sector_id']);
$this->assertSame(\App\Models\Sector::findOrFail($record->sector_id)->name,$sim['sector_id_label']);
$this->assertArrayNotHasKey('locality_id_label',$sim);
$this->assertArrayNotHasKey('admin_post_id_label',$sim);
}
public function test_nonapplicable_land_hides_the_action_and_reopening_recovers_saved_evidence(): void
{
$id = $this->postJson('/investimentos', ['title' => 'QA adaptação sem perda', 'client_key' => (string) Str::uuid(), 'reuse_consent' => true])->assertCreated()->json('id');
$view = $this->getJson('/investimentos/'.$id)->assertOk()->json();
$this->assertNotNull($view['tools']['land']);
$this->assertSame('undetermined', collect($view['steps'])->firstWhere('code', 'S10')['applicability']);
$this->postJson('/investimentos/'.$id.'/etapas/S10', ['version' => $view['case']['version'], 'state' => 'preparing', 'action' => 'prepare', 'reason' => 'Informação territorial já guardada'])->assertOk();
$view = $this->getJson('/investimentos/'.$id)->json();
$facts = ['land_needed' => 'no', 'external_funding' => 'no', 'viability' => 'unknown', 'reason' => 'Investimento sem necessidade de terra nem financiamento externo'];
$this->postJson('/investimentos/'.$id.'/aplicabilidade', ['version' => $view['case']['version'], ...$facts])->assertOk();
$view = $this->getJson('/investimentos/'.$id)->assertJsonPath('tools.land', null)->json();
$step = collect($view['steps'])->firstWhere('code', 'S10');
$this->assertFalse($step['preparation_allowed']);
$this->assertSame('preparing', $step['saved_state']);
$this->assertSame('Informação territorial já guardada', $step['detail']['reason']);
$this->postJson('/investimentos/'.$id.'/etapas/S10', ['version' => $view['case']['version'], 'state' => 'preparing', 'action' => 'prepare', 'reason' => 'Não deve executar etapa sem aplicação'])->assertUnprocessable();
$this->postJson('/investimentos/'.$id.'/aplicabilidade', ['version' => $view['case']['version'], ...$facts, 'land_needed' => 'yes'])->assertOk();
$view = $this->getJson('/investimentos/'.$id)->assertOk()->json();
$this->assertNotNull($view['tools']['land']);
$this->assertSame('preparing', collect($view['steps'])->firstWhere('code', 'S10')['state']);
$this->assertCount(19, $view['steps']);
}
public function test_tempo_configuration_uses_existing_ids_and_maputo_includes_both_units(): void
{
foreach(config('investment_corridors') as $key=>$entry){
$provinces=app(InvestmentCorridorContext::class)->provinces($key);
$this->assertEqualsCanonicalizing($entry['provinces'],array_column($provinces,'source_code'));
$response=$this->getJson('/api/v1/investment/territory-options?type=province&corridor='.$key)->assertOk();
$this->assertEqualsCanonicalizing(array_column($provinces,'id'),array_column($response->json('data'),'id'));
}
$this->assertEqualsCanonicalizing(['Maputo Província','Maputo Cidade'],array_column(app(InvestmentCorridorContext::class)->provinces('maputo'),'name'));
foreach(['nacala','limpopo'] as $key){
$choice=app(InvestmentOpportunityChoices::class)->resolve([['source'=>'opportunity:'.$this->record($key)->id]])[0];
$this->assertSame(app(InvestmentCorridorContext::class)->provinces($key)[0]->id,$choice['territory']['id']);
}
}
public function test_server_refuses_incompatible_location_in_draft_and_confirmation_preview(): void
{
$id=$this->postJson('/investimentos',['title'=>'QA corridor guard','client_key'=>(string)Str::uuid(),'reuse_consent'=>true])->assertCreated()->json('id');
$version=$this->getJson('/investimentos/'.$id.'/contexto')->json('case.version');
$gaza=app(InvestmentCorridorContext::class)->provinces('limpopo')[0]->id;
$body=['version'=>$version,'selection'=>[],'opportunity_choices'=>[['source'=>'opportunity:'.$this->record('beira')->id,'corridor_key'=>'beira','territory'=>'territory:'.$gaza]]];
$this->postJson('/investimentos/'.$id.'/contexto/previa',$body)->assertUnprocessable();
$this->postJson('/investimentos/'.$id.'/contexto/rascunho',[...$body,'draft_sequence'=>1])->assertUnprocessable();
$this->getJson('/api/v1/investment/territory-options?type=district&corridor=beira&parent_id='.$gaza)->assertUnprocessable();
$this->getJson('/investimentos/'.$id.'/contexto')->assertJsonPath('context.revision',0);
}
public function test_encrypted_draft_survives_refresh_and_reauthentication_without_committing_context(): void
{
$id=$this->postJson('/investimentos',['title'=>'QA recovery','client_key'=>(string)Str::uuid(),'reuse_consent'=>true])->assertCreated()->json('id');
$before=$this->getJson('/investimentos/'.$id.'/contexto')->json();
$record=$this->record('beira');$province=app(InvestmentCorridorContext::class)->provinces('beira')[0]->id;
$body=['version'=>$before['case']['version'],'selection'=>[],'declarations'=>['currency'=>'MZN'],'opportunity_choices'=>[['source'=>'opportunity:'.$record->id,'corridor_key'=>'beira','territory'=>'territory:'.$province]]];
$this->postJson('/investimentos/'.$id.'/contexto/rascunho',[...$body,'draft_sequence'=>10])->assertOk();
$this->postJson('/investimentos/'.$id.'/contexto/rascunho',[...$body,'draft_sequence'=>9])->assertConflict();
$encrypted=DB::table('desk_cases')->where('id',$id)->value('payload');$this->assertStringNotContainsString($province,$encrypted);
$this->flushSession();$this->actingAs($this->investor);
$this->get('/investimentos/'.$id.'/contexto/editar')->assertOk()->assertInertia(fn(\Inertia\Testing\AssertableInertia $page)=>$page
->where('draft.declarations.currency','MZN')->where('requestedChoices.0.territory.id',$province)->where('requestedChoices.0.corridor_key','beira'));
$after=$this->getJson('/investimentos/'.$id.'/contexto')->json();
$this->assertSame($before['context'],$after['context']);$this->assertSame($before['case']['version'],$after['case']['version']);
$empty=[...$body,'opportunity_choices'=>[],'entry_sources'=>['opportunity:'.$record->id],'draft_sequence'=>11];
$this->postJson('/investimentos/'.$id.'/contexto/rascunho',$empty)->assertOk();
$this->get('/investimentos/'.$id.'/contexto/editar?source=opportunity:'.$record->id)->assertOk()->assertInertia(fn(\Inertia\Testing\AssertableInertia $page)=>$page->has('requestedChoices',0));
$new=$this->record('nacala');
$this->get('/investimentos/'.$id.'/contexto/editar?source=opportunity:'.$new->id)->assertOk()->assertInertia(fn(\Inertia\Testing\AssertableInertia $page)=>$page->where('requestedChoices.0.opportunity.key','opportunity:'.$new->id));
$this->actingAs(User::factory()->create());$this->postJson('/investimentos/'.$id.'/contexto/rascunho',[...$body,'draft_sequence'=>12])->assertForbidden();
}
public function test_zones_need_compatible_documented_territory_and_catalogue_presence_alone_is_insufficient(): void
{
$service=app(InvestmentCorridorContext::class);
$sofala=collect($service->provinces('beira'))->firstWhere('source_code','07')->id;
$gaza=$service->provinces('limpopo')[0]->id;
$base=app(\App\Services\InvestmentZones::class)->visible()->whereIn('category',['industrial_park','zee','zfi'])->firstOrFail();
$clones=[];
foreach([$sofala,$gaza,null] as $territory){$zone=$base->replicate();$zone->code='QA-'.Str::uuid();$zone->name='Synthetic corridor zone';$zone->primary_territory_id=$territory;$zone->save();$clones[]=$zone;}
$available=array_column($service->parks('beira',$sofala),'id');
$this->assertContains($clones[0]->id,$available);$this->assertNotContains($clones[1]->id,$available);$this->assertNotContains($clones[2]->id,$available);
$id=$this->postJson('/investimentos',['title'=>'QA zone guard','client_key'=>(string)Str::uuid(),'reuse_consent'=>true])->assertCreated()->json('id');
$version=$this->getJson('/investimentos/'.$id.'/contexto')->json('case.version');
$body=['version'=>$version,'selection'=>['park'=>'park:'.$clones[1]->id],'opportunity_choices'=>[['source'=>'opportunity:'.$this->record('beira')->id,'corridor_key'=>'beira','territory'=>'territory:'.$sofala]]];
$this->postJson('/investimentos/'.$id.'/contexto/previa',$body)->assertUnprocessable();
$this->postJson('/investimentos/'.$id.'/contexto/rascunho',[...$body,'draft_sequence'=>1])->assertUnprocessable();
$this->getJson('/investimentos/opcoes-corredor?corridor=beira&territory='.$sofala)->assertOk()->assertJsonFragment(['id'=>$clones[0]->id]);
}
public function test_corridor_change_keeps_compatible_paths_and_removes_only_incompatible_geography(): void
{
$service=app(InvestmentCorridorContext::class);$source='opportunity:'.$this->record('beira')->id;
$sofala=collect($service->provinces('beira'))->firstWhere('source_code','07')->id;
$district=app(\App\Services\InvestmentGeography::class)->publicReferences()->where('parent_id',$sofala)->where('location_type','district')->firstOrFail();
$resolver=app(InvestmentOpportunityChoices::class);
$old=$resolver->resolve([['source'=>$source,'corridor_key'=>'beira','territory'=>'territory:'.$district->id]]);
$same=$resolver->resolve([['source'=>$source,'corridor_key'=>'beira','territory'=>'territory:'.$district->id]],$old);
$this->assertSame($old,$same);
$changed=$resolver->resolve([['source'=>$source,'corridor_key'=>'limpopo']],$old);
$this->assertSame($service->provinces('limpopo')[0]->id,$changed[0]['territory']['id']);
$this->assertCount(1,$changed[0]['territory_path']);
$this->assertNull($this->record('beira')->primary_territory_id);
$this->getJson('/api/v1/investment/territory-options?type=locality')->assertOk()->assertJsonPath('data',[]);
config(['investment_corridors.nacala.provinces'=>['03','99']]);
$this->getJson('/api/v1/investment/territory-options?type=province&corridor=nacala')->assertStatus(503);
}
}
@@ -0,0 +1,143 @@
<?php
namespace Tests\Feature;
use App\Models\Opportunity;
use App\Models\User;
use App\Services\CorridorAgribusinessOpportunities;
use App\Services\InvestorContext;
use App\Services\VirtualDesk;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Tests\TestCase;
final class InvestmentOpportunityChoicesTest extends TestCase
{
use DatabaseTransactions;
private User $investor;
protected function setUp(): void
{
parent::setUp();
$target = DB::selectOne('select current_database() as name, inet_server_port() as port');
$this->assertSame('simulator_fiscal_delta_20261021_test', $target->name);
$this->assertSame(55441, (int) $target->port);
config(['investor-path.enabled' => true, 'investor-path.private_preparation' => true, 'suitecrm.enabled' => false, 'investmoz_ai.enabled' => false]);
$this->withoutVite();
$this->investor = User::factory()->create(); $this->actingAs($this->investor);
}
private function dossier(): string
{
return $this->postJson('/investimentos', ['title' => 'QA: investimento conjunto', 'client_key' => (string) Str::uuid(), 'reuse_consent' => true])->assertCreated()->json('id');
}
public function test_edit_recovers_requested_opportunity_without_presenting_it_as_already_saved(): void
{
$record = Opportunity::where('management_creation_key', app(CorridorAgribusinessOpportunities::class)->entries()[0]['key'])->firstOrFail();
$id = $this->dossier();
$before = $this->getJson('/investimentos/'.$id.'/contexto')->assertOk()->json('context');
$this->get('/investimentos/'.$id.'/contexto/editar?source=opportunity:'.$record->id)->assertOk()
->assertInertia(fn (\Inertia\Testing\AssertableInertia $page) => $page->component('investor-path/context')
->where('source.key', 'opportunity:'.$record->id)
->where('requestedChoices.0.opportunity.key', 'opportunity:'.$record->id)
->where('requestedChoices.0.opportunity.title', $record->title));
$after = $this->getJson('/investimentos/'.$id.'/contexto')->assertOk()->json('context');
$this->assertSame($before, $after);
}
public function test_multiple_opportunities_keep_sites_sources_requirements_and_results_in_one_dossier(): void
{
$entries = array_slice(app(CorridorAgribusinessOpportunities::class)->entries(), 0, 2);
$records = Opportunity::whereIn('management_creation_key', array_column($entries, 'key'))->orderBy('id')->get();
$sites = collect(app(\App\Services\InvestmentCorridorContext::class)->provinces('pemba-lichinga'))->pluck('id');
$this->get('/investimentos/iniciar?opportunities[]='.$records[0]->id.'&opportunities[]='.$records[1]->id)->assertOk();
$id = $this->dossier();
$before = $this->getJson('/investimentos/'.$id.'/contexto')->assertOk()->json();
$input = ['version' => $before['case']['version'], 'selection' => [], 'opportunity_choices' => [
['source' => 'opportunity:'.$records[0]->id, 'territory' => 'territory:'.$sites[0], 'activities' => ['production', 'processing']],
['source' => 'opportunity:'.$records[1]->id, 'territory' => 'territory:'.$sites[1], 'activities' => ['trade']],
]];
$plan = $this->postJson('/investimentos/'.$id.'/contexto/previa', $input)->assertOk()->json();
$this->assertCount(2, $plan['opportunity_choices']);
$this->assertSame($sites[0], $plan['opportunity_choices'][0]['territory']['id']);
$this->assertSame($sites[1], $plan['opportunity_choices'][1]['territory']['id']);
$this->assertSame(CorridorAgribusinessOpportunities::SOURCE_HASH, $plan['opportunity_choices'][1]['source_sha256']);
$this->assertSame('QA: investimento conjunto', $plan['fields']['project_title']);
$this->assertSame($records[0]->summary, $plan['fields']['project_summary']);
$this->assertContains('processing', array_column($plan['requirements_assessment']['requirements'], 'code'));
$this->assertContains('trade', array_column($plan['requirements_assessment']['requirements'], 'code'));
$this->assertSame($before['case']['version'], $this->getJson('/investimentos/'.$id.'/contexto')->json('case.version'));
$control = [...$input, 'preview_hash' => $plan['preview_hash'], 'operation_key' => (string) Str::uuid(), 'confirm' => true];
$this->postJson('/investimentos/'.$id.'/contexto/confirmar', $control)->assertOk();
$this->postJson('/investimentos/'.$id.'/contexto/confirmar', $control)->assertOk()->assertJsonPath('replayed', true);
$after = $this->getJson('/investimentos/'.$id.'/contexto')->assertOk()->json();
$this->assertCount(2, $after['context']['opportunity_choices']);
$snapshot = json_decode($after['prefill']['simulator']['context_applicability_snapshot'], true);
$this->assertSame($after['context']['opportunity_choices'], $snapshot['opportunity_choices']);
$this->assertSame('general', $snapshot['fields']['investment_regime']);
$this->assertArrayNotHasKey('tax_benefit', $snapshot['fields']);
$this->get('/investimentos/'.$id)->assertOk();
$desk = app(VirtualDesk::class); $case = $desk->case($this->investor, $id);
$payload = $desk->decode($case->payload); $resultId = (string) Str::uuid();
$payload['investor_path']['results'] = [['id' => $resultId, 'engine' => 'synthetic-qa-history', 'context_state' => 'current', 'value' => 'preserve']];
DB::table('desk_cases')->where('id', $id)->update(['payload' => $desk->encode($payload)]);
$next = ['version' => $after['case']['version'], 'selection' => [], 'opportunity_choices' => [
['source' => 'opportunity:'.$records[0]->id, 'territory' => 'territory:'.$sites[0], 'activities' => ['production']],
['source' => 'opportunity:'.$records[1]->id, 'territory' => 'territory:'.$sites[0], 'confirm_location_change' => true, 'location_reason' => 'Alternativa territorial escolhida expressamente para comparar o investimento.', 'activities' => ['trade']],
]];
$impact = $this->postJson('/investimentos/'.$id.'/contexto/previa', $next)->assertOk()->json();
$this->assertSame($resultId, $impact['dependent_results'][0]['id']);
$this->postJson('/investimentos/'.$id.'/contexto/confirmar', [...$next, 'preview_hash' => $impact['preview_hash'], 'operation_key' => (string) Str::uuid(), 'confirm' => true])->assertOk();
$this->getJson('/investimentos/'.$id.'/contexto')->assertOk()->assertJsonPath('results.0.value', 'preserve')->assertJsonPath('results.0.context_state', 'review_required');
$this->actingAs(User::factory()->create())->getJson('/investimentos/'.$id.'/contexto')->assertForbidden();
}
public function test_documented_location_cannot_change_silently_and_stale_source_cannot_be_confirmed(): void
{
$record = Opportunity::where('management_creation_key', app(CorridorAgribusinessOpportunities::class)->entries()[0]['key'])->firstOrFail();
$sites = collect(app(\App\Services\InvestmentCorridorContext::class)->provinces('pemba-lichinga'))->pluck('id');
$record->primary_territory_id = $sites[0]; $record->save();
$id = $this->dossier(); $version = $this->getJson('/investimentos/'.$id.'/contexto')->json('case.version');
$input = ['version' => $version, 'selection' => [], 'opportunity_choices' => [['source' => 'opportunity:'.$record->id, 'territory' => 'territory:'.$sites[1]]]];
$this->postJson('/investimentos/'.$id.'/contexto/previa', $input)->assertUnprocessable();
$this->postJson('/investimentos/'.$id.'/contexto/previa', ['version' => $version, 'selection' => ['opportunity' => 'opportunity:'.$record->id, 'territory' => 'territory:'.$sites[1]]])->assertUnprocessable();
$input['opportunity_choices'][0]['confirm_location_change'] = true;
$input['opportunity_choices'][0]['location_reason'] = 'Escolha explícita de implantação alternativa, sem alterar a origem.';
$plan = $this->postJson('/investimentos/'.$id.'/contexto/previa', $input)->assertOk()->json();
$this->assertSame($sites[0], $record->fresh()->primary_territory_id);
$this->postJson('/investimentos/'.$id.'/contexto/confirmar', [...$input, 'preview_hash' => $plan['preview_hash'], 'operation_key' => (string) Str::uuid(), 'confirm' => true])->assertOk();
$input['version'] = $this->getJson('/investimentos/'.$id.'/contexto')->json('case.version');
unset($input['opportunity_choices'][0]['confirm_location_change']);
$plan = $this->postJson('/investimentos/'.$id.'/contexto/previa', $input)->assertOk()->assertJsonPath('dependency_changed', false)->json();
$record->title .= ' QA source changed'; $record->save();
$this->postJson('/investimentos/'.$id.'/contexto/confirmar', [...$input, 'preview_hash' => $plan['preview_hash'], 'operation_key' => (string) Str::uuid(), 'confirm' => true])->assertConflict();
$this->getJson('/investimentos/'.$id.'/contexto')->assertOk()->assertJsonPath('context.revision', 1);
}
public function test_the_34_choices_and_sources_fit_the_existing_simulation_draft_without_reentry(): void
{
$records = Opportunity::whereIn('management_creation_key', array_column(app(CorridorAgribusinessOpportunities::class)->entries(), 'key'))->orderBy('id')->get();
$this->assertCount(34, $records);
$id = $this->dossier();
$view = $this->getJson('/investimentos/'.$id.'/contexto')->assertOk()->json();
$input = ['version' => $view['case']['version'], 'selection' => [], 'opportunity_choices' => $records->map(fn ($r) => ['source' => 'opportunity:'.$r->id])->all()];
$plan = $this->postJson('/investimentos/'.$id.'/contexto/previa', $input)->assertOk()->json();
$this->postJson('/investimentos/'.$id.'/contexto/confirmar', [...$input, 'preview_hash' => $plan['preview_hash'], 'operation_key' => (string) Str::uuid(), 'confirm' => true])->assertOk();
$view = $this->getJson('/investimentos/'.$id.'/contexto')->assertOk()->json();
$snapshot = $view['prefill']['simulator']['context_applicability_snapshot'];
$this->assertCount(34, json_decode($snapshot, true)['opportunity_choices']);
$this->assertLessThan(1000000, strlen($snapshot));
// Mirror the existing browser partition: labels are persisted under selection_labels.
$economic = [...$view['prefill']['simulator'], 'purpose' => 'simulate_only', 'currency' => 'MZN'];
foreach (array_keys($economic) as $field) if (str_ends_with($field, '_label')) {
$economic['selection_labels'][substr($field, 0, -6)] = $economic[$field]; unset($economic[$field]);
}
$draft = $this->postJson('/api/v1/simulation-drafts', ['context' => $id, 'personal_snapshot' => [],
'economic_snapshot' => $economic], ['Idempotency-Key' => (string) Str::uuid()])->assertCreated()->json();
$this->assertNotEmpty($draft);
$this->getJson('/investimentos/'.$id.'/contexto')->assertOk()->assertJsonPath('context.opportunity_choices.33.opportunity.key', 'opportunity:'.$records[33]->id);
}
}
@@ -0,0 +1,130 @@
<?php
namespace Tests\Feature;
use App\Http\Controllers\InvestmentPortfolioController;
use App\Models\Opportunity;
use App\Models\User;
use App\Services\CorridorAgribusinessOpportunities;
use App\Services\InvestmentCorridorContext;
use App\Services\InvestmentOpportunityChoices;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Inertia\Testing\AssertableInertia as Assert;
use Tests\TestCase;
final class InvestmentPortfolioContinuityTest extends TestCase
{
use DatabaseTransactions;
public function test_draft_version_guard_still_rejects_a_real_stale_version(): void
{
$draft = new \App\Models\SimulationFormDraft;
$draft->forceFill(['id'=>(string)Str::uuid(),'version_no'=>2]);
$service=app(\App\Services\SimulationFormDrafts::class);
$guard=new \ReflectionMethod($service,'requireMatch');
$tag=$service->etag($draft);
foreach (['W/'.$tag, str_replace('-v2','-v1',$tag)] as $invalid) {
$request=\Illuminate\Http\Request::create('/','PUT'); $request->headers->set('If-Match',$invalid);
try { $guard->invoke($service,$request,$draft); $this->fail('A mismatching version must be rejected'); }
catch (\Symfony\Component\HttpKernel\Exception\HttpException $error) { $this->assertSame(412,$error->getStatusCode()); }
}
$request=\Illuminate\Http\Request::create('/','PUT'); $request->headers->set('If-Match',$tag);
$this->assertNull($guard->invoke($service,$request,$draft));
}
public function test_beira_documentary_membership_opens_the_same_published_opportunities_without_inferring_by_province(): void
{
$portfolio = InvestmentPortfolioController::visible()->where('content->code', 'COR-02')->firstOrFail();
$service = app(CorridorAgribusinessOpportunities::class);
$ids = $service->portfolioOpportunityIds($portfolio);
$this->assertCount(7, $ids);
$this->get('/carteira/'.$portfolio->id)->assertOk()->assertInertia(fn(Assert $page) => $page->has('opportunities', 7));
$catalogueIds=app(\App\Services\InvestmentCatalogue::class)->query(['corridor_id'=>$portfolio->corridor_id])->pluck('opportunities.id')->all();
$this->assertSame([], array_values(array_diff($ids,$catalogueIds)));
$opportunity=Opportunity::findOrFail($ids[0]);
$this->get('/oportunidades/'.$opportunity->slug.'?from_dossier='.$portfolio->id)->assertOk();
$origins=app(InvestmentOpportunityChoices::class)->portfolioOrigins([$portfolio->id],$opportunity->id);
$this->assertSame($portfolio->id,$origins[0]['id']);
$this->assertSame('corridor',$origins[0]['kind']);
$other=Opportunity::published()->whereNotIn('id',$ids)->firstOrFail();
$this->get('/oportunidades/'.$other->slug.'?from_dossier='.$portfolio->id)->assertNotFound();
$this->assertSame([], $service->portfolioOpportunityIds(InvestmentPortfolioController::visible()->whereNotNull('project_id')->firstOrFail()));
}
private User $actor;
protected function setUp(): void
{
parent::setUp(); $this->withoutVite();
$db=DB::selectOne('select current_database() as name,inet_server_port() as port');
$this->assertSame('simulator_fiscal_delta_20261021_test',$db->name); $this->assertSame(55441,(int)$db->port);
config(['investor-path.enabled'=>true,'suitecrm.enabled'=>false,'investmoz_ai.enabled'=>false]);
$this->actor=User::factory()->create(); $this->actingAs($this->actor);
}
private function records(): array
{
$entries=collect(app(CorridorAgribusinessOpportunities::class)->entries())->where('corridor',config('investment_corridors.beira.name'))->take(2);
$opportunities=Opportunity::whereIn('management_creation_key',$entries->pluck('key'))->orderBy('id')->get();
$project=InvestmentPortfolioController::visible()->whereNotNull('project_id')->firstOrFail();
$cluster=InvestmentPortfolioController::visible()->whereNotNull('cluster_id')->firstOrFail();
foreach($opportunities as $opportunity) {
// Synthetic links exist only inside this rolled-back QA transaction.
DB::table('opportunity_projects')->insertOrIgnore(['opportunity_id'=>$opportunity->id,'project_id'=>$project->project_id]);
DB::table('opportunity_clusters')->insertOrIgnore(['opportunity_id'=>$opportunity->id,'cluster_id'=>$cluster->cluster_id]);
}
return [$opportunities,$project,$cluster];
}
public function test_origin_and_location_survive_entry_draft_confirmation_and_simulator_without_resetting_deliberate_values(): void
{
[$records,$origin,$cluster]=$this->records(); $opportunity=$records[0];
$province=collect(app(InvestmentCorridorContext::class)->provinces('beira'))->firstWhere('source_code','07')->id;
$query='?source=opportunity:'.$opportunity->id.'&from_dossier='.$origin->id.'&province_id='.$province;
auth()->logout();
$this->get('/investimentos/iniciar'.$query)->assertRedirect(route('login'));
parse_str(parse_url(session('url.intended'),PHP_URL_QUERY),$intended);
$this->assertSame($origin->id,$intended['from_dossier']);$this->assertSame($province,$intended['province_id']);
$this->actingAs($this->actor);
$this->get('/investimentos/iniciar'.$query)->assertOk()->assertInertia(fn(Assert $p)=>$p->where('opportunityChoices.0.portfolio_origins.0.id',$origin->id)->where('opportunityChoices.0.territory.id',$province)->where('entryQuery.from_dossier',$origin->id));
$id=$this->postJson('/investimentos',['title'=>'QA título deliberado','client_key'=>(string)Str::uuid(),'reuse_consent'=>true])->assertCreated()->json('id');
$body=['version'=>$this->getJson('/investimentos/'.$id.'/contexto')->json('case.version'),'selection'=>[],'opportunity_choices'=>[
['source'=>'opportunity:'.$opportunity->id,'territory'=>'territory:'.$province,'portfolio_origins'=>[$origin->id],'activities'=>['trade']],
['source'=>'opportunity:'.$records[1]->id,'territory'=>'territory:'.$province,'portfolio_origins'=>[$origin->id,$cluster->id]],
]];
$this->postJson('/investimentos/'.$id.'/contexto/rascunho',[...$body,'draft_sequence'=>1])->assertOk();
$this->flushSession();$this->actingAs($this->actor);
$this->get('/investimentos/'.$id.'/contexto/editar')->assertOk()->assertInertia(fn(Assert $p)=>$p->where('requestedChoices.0.portfolio_origins.0.id',$origin->id)->where('requestedChoices.0.territory.id',$province));
$preview=$this->postJson('/investimentos/'.$id.'/contexto/previa',$body)->assertOk()->json();
$this->assertGreaterThan(0,$preview['opportunity_choices'][0]['portfolio_origins'][0]['version']);
$this->assertSame('QA título deliberado',$preview['fields']['project_title']);
$this->postJson('/investimentos/'.$id.'/contexto/confirmar',[...$body,'preview_hash'=>$preview['preview_hash'],'operation_key'=>(string)Str::uuid(),'confirm'=>true])->assertOk();
$view=$this->getJson('/investimentos/'.$id.'/contexto')->assertOk()->json();
$this->assertSame($view['context']['opportunity_choices'],json_decode($view['prefill']['simulator']['context_applicability_snapshot'],true)['opportunity_choices']);
$this->assertSame($province,$view['prefill']['simulator']['province_id']);
$this->assertSame('QA título deliberado',$view['prefill']['simulator']['project_title']);
$created=$this->postJson('/api/v1/simulation-drafts',['context'=>$id,'personal_snapshot'=>[],'economic_snapshot'=>['purpose'=>'simulate_only','project_title'=>'QA edição deliberada no simulador','context_applicability_snapshot'=>$view['prefill']['simulator']['context_applicability_snapshot']]],['Idempotency-Key'=>(string)Str::uuid()])->assertCreated();
$this->flushSession();$this->actingAs($this->actor);
$simulation=$this->getJson('/api/v1/simulation-drafts/'.$created->json('data.id').'?context='.$id)->assertOk()->assertJsonPath('data.economic_snapshot.project_title','QA edição deliberada no simulador')->json('data.economic_snapshot');
$this->assertSame($view['context']['opportunity_choices'],json_decode($simulation['context_applicability_snapshot'],true)['opportunity_choices']);
$manica=collect(app(InvestmentCorridorContext::class)->provinces('beira'))->firstWhere('source_code','06')->id;
$this->get('/investimentos/'.$id.'/contexto/editar?source=opportunity:'.$opportunity->id.'&from_dossier='.$cluster->id.'&province_id='.$manica)
->assertOk()->assertInertia(fn(Assert $p)=>$p->where('requestedChoices.0.territory.id',$province)->where('requestedChoices.0.activities.0','trade')->has('requestedChoices',2)->has('requestedChoices.0.portfolio_origins',2)->has('entryLocationDifferences',1));
$body['version']=$this->getJson('/investimentos/'.$id.'/contexto')->json('case.version');
$body['opportunity_choices'][0]['portfolio_origins'][]=$cluster->id;
$associations=$this->postJson('/investimentos/'.$id.'/contexto/previa',$body)->assertOk()->json();
$this->assertFalse($associations['calculation_inputs_changed']);$this->assertSame([],$associations['review_steps']);
$this->postJson('/investimentos/'.$id.'/contexto/confirmar',[...$body,'preview_hash'=>$associations['preview_hash'],'operation_key'=>(string)Str::uuid(),'confirm'=>true])->assertOk();
$this->getJson('/investimentos/'.$id.'/contexto')->assertJsonPath('context.dependency_hash',$view['context']['dependency_hash'])->assertJsonCount(2,'context.opportunity_choices.0.portfolio_origins');
}
public function test_unrelated_or_private_origins_are_rejected_in_entry_draft_and_preview(): void
{
[$records,$origin]=$this->records();
DB::table('opportunity_projects')->where('opportunity_id',$records[0]->id)->where('project_id',$origin->project_id)->delete();
$this->get('/investimentos/iniciar?source=opportunity:'.$records[0]->id.'&from_dossier='.$origin->id)->assertUnprocessable();
$id=$this->postJson('/investimentos',['title'=>'QA origem inválida','client_key'=>(string)Str::uuid(),'reuse_consent'=>true])->json('id');
$body=['version'=>$this->getJson('/investimentos/'.$id.'/contexto')->json('case.version'),'selection'=>[],'opportunity_choices'=>[['source'=>'opportunity:'.$records[0]->id,'portfolio_origins'=>[$origin->id]]]];
$this->postJson('/investimentos/'.$id.'/contexto/rascunho',[...$body,'draft_sequence'=>1])->assertUnprocessable();
$this->postJson('/investimentos/'.$id.'/contexto/previa',$body)->assertUnprocessable();
$origin->publication_status='draft';$origin->save();
$this->get('/investimentos/iniciar?source=opportunity:'.$records[1]->id.'&from_dossier='.$origin->id)->assertNotFound();
}
}
@@ -0,0 +1,30 @@
<?php
namespace Tests\Feature;
use Illuminate\Support\Facades\DB;
use Inertia\Testing\AssertableInertia as Assert;
use Tests\TestCase;
class OpportunityCorridorExplorerTest extends TestCase
{
public function test_the_public_agribusiness_catalogue_groups_all_34_records_without_page_truncation(): void
{
$target = DB::selectOne('select current_database() as name, inet_server_port() as port');
$this->assertSame('simulator_fiscal_delta_20261021_test', $target->name);
$this->assertSame(55441, (int) $target->port);
$this->withoutVite();
$this->get('/oportunidades?sector=agronegocio&view=corridors')->assertOk()->assertInertia(fn (Assert $page) => $page
->component('investment/catalogue')
->has('corridorGroups', 6)
->where('results.total', 34)
->where('corridorGroups', function ($groups) {
$groups = collect($groups);
$ids = $groups->flatMap(fn ($g) => collect($g['opportunities'])->pluck('id'));
return $ids->count() === 34 && $ids->unique()->count() === 34
&& $groups->pluck('count', 'name')->get('Corredor da Beira') === 7
&& $groups->pluck('count', 'name')->get('Corredor de Maputo') === 5
&& ! str_contains(json_encode($groups), 'source_sha256');
}));
}
}
+170
View File
@@ -0,0 +1,170 @@
<?php
namespace Tests\Feature;
use App\Models\Opportunity;
use App\Models\User;
use App\Services\OpportunityManagement;
use App\Services\PortalProfileAdministration;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Tests\TestCase;
final class OpportunityPromotionTest extends TestCase
{
use DatabaseTransactions;
private User $actor;
private Opportunity $record;
private string $org;
protected function setUp(): void
{
parent::setUp();
$target = DB::selectOne('select current_database() as name, inet_server_port() as port');
$this->assertSame('simulator_fiscal_delta_20261021_test', $target->name);
$this->assertSame(55441, (int) $target->port);
$this->withoutVite();
config(['portal.profile_access_enabled' => true, 'portal.managers' => []]);
$this->actor = User::factory()->create();
$this->actor->forceFill(['two_factor_secret' => Crypt::encryptString('JBSWY3DPEHPK3PXP'), 'two_factor_confirmed_at' => now()])->save();
$this->org = (string) Str::uuid();
DB::table('organizations')->insert(['id' => $this->org, 'name' => 'SYNTHETIC QA Tempo operator', 'external_id' => '000000000', 'organization_type' => 'private']);
DB::table('audit_events')->insert(['source_system' => 'portal-profiles-v1', 'entity_type' => 'authorization', 'action' => 'operator_organization_registered', 'data_classification' => 'restricted',
'after_data' => json_encode(['organization_id' => $this->org, 'organization' => ['name' => 'SYNTHETIC QA Tempo operator', 'nuit' => '000000000'], 'source_reference' => 'Synthetic QA designation only'])]);
app(PortalProfileAdministration::class)->insertGrant((string) Str::uuid(), User::factory()->create(), ['user_id' => $this->actor->id,
'role' => 'promoter', 'organization_id' => $this->org, 'opportunity_types' => OpportunityManagement::CATEGORIES, 'responsibilities' => [], 'source' => 'internal-portal-designation']);
$this->record = new Opportunity;
$this->record->forceFill(['title' => 'PRIVATE TITLE', 'slug' => 'qa-promo-'.Str::uuid(), 'reference' => 'QA-'.Str::uuid(), 'status' => 'draft',
'opportunity_category' => 'investment_project', 'owner_user_id' => $this->actor->id, 'organization_id' => $this->org, 'management_version' => 1,
'promoter_name' => 'PRIVATE CONTACT', 'description' => 'PRIVATE CONTENT', 'estimated_investment_amount' => '123456.00',
'workflow_context' => ['operator_organization_id' => $this->org, 'contributors' => [$this->actor->id]],
'management_draft' => json_encode(['canonical' => ['official_title' => 'PRIVATE TITLE', 'contact' => 'PRIVATE CONTACT', 'documents' => ['PRIVATE DOCUMENT']]])])->save();
$this->login($this->actor);
}
private function login(User $actor): void
{
$this->actingAs($actor)->withSession(['portal.mfa_actor' => $actor->id, 'portal.mfa_at' => time(), 'portal.access_revision' => (int) $actor->portal_access_revision]);
}
private function base(): string { return '/minhas-oportunidades/'.$this->record->id.'/divulgacao'; }
private function data(array $overrides = []): array
{
return [...['etag' => app(OpportunityManagement::class)->etag($this->record), 'action' => 'disclose', 'title' => 'SYNTHETIC QA public summary',
'summary' => 'Synthetic QA opportunity in preparation, no real investment.', 'description' => 'Only this deliberately selected public text.',
'source_reference' => 'Synthetic source provided for QA', 'edition' => 'QA-1', 'source_date' => '2026-10-22',
'limitations' => 'Financing and implementation not established.', 'territory_ids' => []], ...$overrides];
}
private function preview(array $overrides = []): array { return $this->postJson($this->base().'/previa', $this->data($overrides))->assertOk()->json(); }
private function confirm(array $preview, ?string $key = null): array
{
$data = ['token' => $preview['token'], 'operation_key' => $key ?? (string) Str::uuid(), 'confirm' => true, 'reason' => 'Synthetic QA operation reviewed in preview'];
$this->postJson($this->base().'/confirmar', $data)->assertOk();
return $data;
}
public function test_preview_publish_update_withdraw_and_redisclose_preserve_private_draft_and_audit(): void
{
$file = (string) Str::uuid();
$attachment = (string) Str::uuid();
DB::table('documents')->insert(['id' => $file, 'file_name' => 'PRIVATE-QA-DOCUMENT.txt', 'storage_key' => 'private-qa/'.$file,
'media_type' => 'text/plain', 'sha256' => hash('sha256', 'private QA only'), 'size_bytes' => 15,
'uploaded_by' => $this->actor->id, 'status' => 'quarantined', 'data_classification' => 'internal']);
DB::table('opportunity_documents')->insert(['id' => $attachment, 'opportunity_id' => $this->record->id, 'document_id' => $file,
'version' => 1, 'created_by' => $this->actor->id, 'payload' => json_encode(['title' => 'PRIVATE DOCUMENT', 'visibility' => 'internal'])]);
$documentBefore = DB::table('documents')->where('id', $file)->first();
$attachmentBefore = DB::table('opportunity_documents')->where('id', $attachment)->first();
DB::table('opportunity_profiles')->insert(['opportunity_id' => $this->record->id, 'business_model' => 'PRIVATE PROFILE', 'source_reference' => 'PRIVATE SOURCE']);
$before = $this->record->refresh()->getRawOriginal();
$private = $before['management_draft'];
$preview = $this->preview();
$this->assertSame($before, $this->record->fresh()->getRawOriginal());
$this->assertFalse($preview['writes']);
$this->assertStringContainsString('Em preparação', $preview['projection']['description']);
$this->assertStringNotContainsString('PRIVATE', json_encode($preview['projection']));
$key = (string) Str::uuid();
$request = $this->confirm($preview, $key);
$this->postJson($this->base().'/confirmar', $request)->assertOk()->assertJsonPath('replayed', true);
$this->record->refresh();
$this->assertSame('draft', $this->record->workflow_context['working_state']);
$this->assertSame($private, $this->record->management_draft);
$this->assertSame('PRIVATE CONTACT', app(OpportunityManagement::class)->values($this->record)['promoter_name']);
$public = $this->getJson('/api/v1/opportunities/'.$this->record->slug)->assertOk();
$this->assertStringNotContainsString('PRIVATE', $public->getContent());
$public->assertJsonPath('data.maturity_label', 'Em preparação')->assertJsonPath('data.estimated_investment_amount', null);
$context = app(\App\Services\InvestmentCatalogue::class)->context($this->record->id);
$this->assertNull($context['profile']);
$this->assertSame([], $context['source_evidence']);
$this->assertStringNotContainsString('PRIVATE', json_encode($context));
$this->confirm($this->preview(['summary' => 'Updated synthetic QA summary.']));
$this->getJson('/api/v1/opportunities/'.$this->record->slug)->assertOk()->assertJsonPath('data.summary', 'Updated synthetic QA summary.');
$this->get($this->base())->assertOk();
$this->confirm($this->preview(['action' => 'withdraw']));
$this->getJson('/api/v1/opportunities/'.$this->record->slug)->assertNotFound();
$this->get($this->base())->assertOk();
$this->confirm($this->preview());
$this->getJson('/api/v1/opportunities/'.$this->record->slug)->assertOk();
$events = DB::table('audit_events')->where('source_system', 'tempo-preparation-publication')->where('after_data->record_id', $this->record->id)->get();
$this->assertCount(4, $events);
$this->assertContains('promotional_summary_withdrawn', $events->pluck('action')->all());
foreach ($events as $event) { $this->assertSame($this->actor->id, (int) $event->actor_user_id); $this->assertFalse(json_decode($event->after_data, true)['execution_approved']); }
$this->assertEquals($documentBefore, DB::table('documents')->where('id', $file)->first());
$this->assertEquals($attachmentBefore, DB::table('opportunity_documents')->where('id', $attachment)->first());
}
public function test_stale_tampered_expired_and_foreign_preview_do_not_write(): void
{
$preview = $this->preview();
$payload = ['token' => $preview['token'].'broken', 'operation_key' => (string) Str::uuid(), 'confirm' => true, 'reason' => 'Synthetic QA invalid confirmation'];
$this->postJson($this->base().'/confirmar', $payload)->assertUnprocessable();
$this->record->title = 'Concurrent private edit'; $this->record->save();
$this->postJson($this->base().'/confirmar', [...$payload, 'token' => $preview['token']])->assertConflict();
$fresh = $this->preview();
$this->travel(16)->minutes();
$this->withSession(['portal.mfa_at' => time()]);
$this->postJson($this->base().'/confirmar', [...$payload, 'token' => $fresh['token']])->assertConflict();
$this->travelBack();
$this->login(User::factory()->create());
$this->get($this->base())->assertForbidden();
$this->postJson($this->base().'/confirmar', [...$payload, 'token' => $fresh['token']])->assertForbidden();
$this->assertSame('draft', $this->record->fresh()->status);
}
public function test_external_promoter_and_unrelated_internal_actor_cannot_disclose_and_no_ordinary_publication_is_overwritten(): void
{
$other = User::factory()->create();
$this->login($other);
$this->postJson($this->base().'/previa', $this->data())->assertForbidden();
$this->login($this->actor);
DB::table('memberships')->insert(['id' => (string) Str::uuid(), 'organization_id' => $this->org, 'user_id' => $this->actor->id, 'role' => 'officer']);
DB::table('portal_role_assignments')->where('user_id', $this->actor->id)->update(['source' => 'approved-external-promoter']);
$this->postJson($this->base().'/previa', $this->data())->assertForbidden();
DB::table('portal_role_assignments')->where('user_id', $this->actor->id)->update(['source' => 'internal-portal-designation']);
$this->withSession(['portal.mfa_at' => time() - 901]);
$this->postJson($this->base().'/previa', $this->data())->assertForbidden();
$this->login($this->actor);
$this->record->forceFill(['status' => 'published', 'published_at' => now(), 'workflow_context' => [...$this->record->workflow_context, 'publication' => ['visibility' => 'public']]])->save();
$this->get($this->base())->assertForbidden();
}
public function test_existing_publisher_scope_can_use_the_same_action_without_new_role_or_approval(): void
{
$publisher = User::factory()->create();
$publisher->forceFill(['two_factor_secret' => Crypt::encryptString('JBSWY3DPEHPK3PXP'), 'two_factor_confirmed_at' => now()])->save();
app(PortalProfileAdministration::class)->insertGrant((string) Str::uuid(), $this->actor, ['user_id' => $publisher->id,
'role' => 'publisher', 'organization_id' => $this->org, 'opportunity_types' => ['investment_project'], 'responsibilities' => [], 'source' => 'synthetic-approved-designation']);
$roles = DB::table('portal_role_assignments')->count();
$this->login($publisher);
$access = app(\App\Services\PortalProfileAccess::class);
$this->assertTrue($access->canView($publisher, $this->record), 'Publisher read scope');
$this->assertTrue($access->scoped($publisher, 'publisher', $this->org, 'investment_project'), 'Publisher scope');
$this->assertTrue(app(\App\Services\OpportunityPromotion::class)->allowed($publisher, $this->record), 'Promotional action scope');
$this->confirm($this->preview());
$this->assertSame($roles, DB::table('portal_role_assignments')->count());
$this->assertArrayNotHasKey('approval', $this->record->fresh()->workflow_context);
}
public function test_profile_directory_uses_existing_accounts_without_exposing_authentication_secrets(): void
{
$other = User::factory()->create();
DB::table('portal_superuser_pair')->updateOrInsert(['id' => 1], ['first_user_id' => $this->actor->id, 'second_user_id' => $other->id, 'approved_by' => $other->id]);
$this->get('/perfis/gestao')->assertOk()->assertInertia(fn (\Inertia\Testing\AssertableInertia $page) => $page
->has('staffAccounts')->where('staffAccounts', fn ($rows) => collect($rows)->contains(fn ($row) => $row['id'] === $this->actor->id && $row['email'] === $this->actor->email && $row['verified'] && $row['mfa'])
&& collect($rows)->every(fn ($row) => !isset($row['password']) && !isset($row['two_factor_secret']))));
}
}
+48
View File
@@ -0,0 +1,48 @@
<?php
namespace Tests\Unit;
use App\Services\CorridorAgribusiness;
use DOMDocument;
use DOMXPath;
use PHPUnit\Framework\TestCase;
class CorridorAgribusinessTest extends TestCase
{
public function test_each_named_corridor_keeps_its_own_opportunities_and_original_content(): void
{
$data = json_decode(file_get_contents(__DIR__.'/../../database/data/corridor-investment-opportunities.json'), true, 512, JSON_THROW_ON_ERROR);
$original = '';
foreach ($data['groups'] as $index => $group) {
$original .= '<p><strong>'.chr(97 + $index).') '.$group['name'].'</strong></p><p>Cadeias de valor prioritárias:<br>Conteúdo original '.$index.'</p><p>Descrição preservada '.$index.'</p>';
}
$original .= '<p><strong>4. Âmbito de Investimento</strong></p><p>Outros sectores serão acrescentados.</p>';
$service = new CorridorAgribusiness;
$html = $service->enrich($original);
$doc = new DOMDocument;
$doc->loadHTML('<?xml encoding="UTF-8">'.$html);
$xpath = new DOMXPath($doc);
$this->assertCount(6, $xpath->query('//ul[starts-with(@id,"agronegocio-oportunidades-")]'));
$this->assertCount(34, $xpath->query('//ul[starts-with(@id,"agronegocio-oportunidades-")]/li'));
foreach ($data['groups'] as $index => $group) {
$list = $doc->getElementById('agronegocio-oportunidades-'.($index + 1));
$this->assertSame(count($group['opportunities']), $list->getElementsByTagName('li')->length);
foreach ($group['opportunities'] as $opportunity) {
$this->assertStringContainsString($opportunity['description'], $list->textContent);
}
$this->assertStringContainsString(chr(97 + $index).') '.$group['name'], $html);
$this->assertStringContainsString('Conteúdo original '.$index, $html);
$this->assertStringContainsString('Descrição preservada '.$index, $html);
}
$this->assertStringContainsString('4. Âmbito de Investimento', $html);
$this->assertSame($html, $service->enrich($html));
}
public function test_broader_corridor_names_and_incomplete_sections_are_left_unchanged(): void
{
$html = '<p>Corredor de Nacala e Norte</p><p>Cadeias de valor prioritárias: preservadas</p>'
.'<p>Corredor Maputo–Limpopo</p><p>Cadeias de valor prioritárias: preservadas</p>'
.'<p>Corredor de Nacala</p><p>Sem parágrafo de cadeias prioritárias.</p>';
$this->assertSame($html, (new CorridorAgribusiness)->enrich($html));
}
}
@@ -0,0 +1,36 @@
<?php
namespace Tests\Unit;
use App\Models\Opportunity;
use App\Services\InvestmentCatalogue;
use App\Services\OpportunityCorridorExplorer;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Mockery;
use Tests\TestCase;
class OpportunityCorridorExplorerTest extends TestCase
{
public function test_multiple_groups_keep_one_identity_and_unassociated_records_remain_accessible(): void
{
$first = new Opportunity(['title' => 'Primeira', 'province' => 'Sofala', 'district' => 'Dondo']);
$first->id = 101;
$other = new Opportunity(['title' => 'Outra']); $other->id = 102;
$query = Mockery::mock(Builder::class);
$query->shouldReceive('get')->once()->andReturn(new Collection([$first, $other]));
$catalogue = Mockery::mock(InvestmentCatalogue::class);
$filters = ['sector' => 'agronegocio', 'view' => 'corridors'];
$catalogue->shouldReceive('query')->with($filters)->once()->andReturn($query);
$catalogue->shouldReceive('context')->with(101)->once()->andReturn(['corridors' => collect([(object)['id'=>'a','name'=>'Corredor A'],(object)['id'=>'b','name'=>'Corredor B']])]);
$catalogue->shouldReceive('context')->with(102)->once()->andReturn(['corridors'=>collect()]);
$this->app->instance(InvestmentCatalogue::class, $catalogue);
$groups = app(OpportunityCorridorExplorer::class)->groups($filters);
$this->assertSame(['Corredor A','Corredor B','Outras localizações'], array_column($groups,'name'));
$this->assertSame([101,101,102], array_map(fn($g)=>$g['opportunities'][0]['id'], $groups));
$this->assertSame([1,1,1], array_column($groups,'count'));
$this->assertSame(['Sofala / Dondo'], $groups[0]['locations']);
$this->assertSame([], $groups[2]['locations']);
$this->assertSame([], $groups[0]['opportunities'][0]['stages']);
}
}
+28
View File
@@ -0,0 +1,28 @@
<?php
namespace Tests\Unit;
use App\Models\Opportunity;
use App\Services\CorridorAgribusinessOpportunities;
use App\Services\OpportunityPublicText;
use Tests\TestCase;
class OpportunityPublicTextTest extends TestCase
{
public function test_public_serialization_omits_generated_internal_notes_without_changing_original_or_other_content(): void
{
$source = ['source' => 'investimentos nos corredores.docx', 'source_sha256' => CorridorAgribusinessOpportunities::SOURCE_HASH, 'corridor' => 'Corredor de Maputo'];
$body = 'Produção de arroz.'."\n\nFonte: ".$source['source'].'; SHA-256 '.$source['source_sha256']."\n\n".CorridorAgribusinessOpportunities::LIMITATIONS;
$record = new Opportunity(['description' => $body]);
$record->workflow_context = ['corridor_agribusiness' => $source];
$public = $record->toArray();
$this->assertStringContainsString('Produção de arroz.', $public['description']);
$this->assertStringNotContainsString('SHA-256', $public['description']);
$this->assertStringNotContainsString('.docx', $public['description']);
$this->assertSame($body, $record->description);
$this->assertArrayNotHasKey('workflow_context', $public);
$other = new Opportunity(['description' => $body]);
$this->assertSame($body, OpportunityPublicText::description($other, $body));
$this->assertSame('Texto revisto pela equipa.', OpportunityPublicText::description($record, 'Texto revisto pela equipa.'));
}
}
+47 -1
View File
@@ -7,11 +7,57 @@ test('legacy draft without a compatible source keeps its shape and identity',()=
assert.equal(Object.hasOwn(inheritSimulatorFields(legacy),'compatible_fields_snapshot'),false);
});
import test from 'node:test';
import { inheritCompatibleFields, inheritSimulatorFields } from '../../resources/js/lib/context-inheritance.ts';
import { inheritCompatibleFields, inheritSimulatorFields, reusedInvestmentValue } from '../../resources/js/lib/context-inheritance.ts';
import { serializeBusinessPlanSnapshot } from '../../resources/js/lib/business-financial-plan.ts';
import { monthlySalesFromDraft } from '../../resources/js/lib/monthly-iva.ts';
import { restoreSimulationDraft, partitionSimulationDraft } from '../../resources/js/lib/simulation-form.ts';
test('known investment questions become editable summaries after save/recovery, while missing and deliberate edits remain questions', () => {
const scope='dossier:A:simulator';
const source={project_title:'Florestas — Beira',project_summary:'Preparar investimento florestal',province_id:'p1',province_id_label:'Sofala',sector_id:'3',sector_id_label:'Agronegócio'};
const reused=inheritCompatibleFields(scope,{},source,null);
const draft={...reused.patch,context_reuse:reused.metadata,purpose:'simulate_only',journey_type:'investor'};
const saved=partitionSimulationDraft(draft);
const recovered=restoreSimulationDraft(saved.personal_snapshot,saved.economic_snapshot);
assert.equal(reusedInvestmentValue(scope,'project_title',recovered),source.project_title);
assert.equal(reusedInvestmentValue(scope,'province_id',recovered),'Sofala');
assert.equal(reusedInvestmentValue(scope,'sector_id',recovered),'Agronegócio');
assert.equal(reusedInvestmentValue(scope,'district_id',recovered),null);
assert.equal(reusedInvestmentValue('dossier:B:simulator','project_title',recovered),null);
assert.equal(reusedInvestmentValue(scope,'project_title',{...recovered,project_title:'Edição deliberada'}),null);
assert.equal(reusedInvestmentValue(scope,'project_title',{...recovered,project_title:''}),null);
assert.equal(reusedInvestmentValue(scope,'province_id',{...recovered,province_id_label:''}),null);
assert.equal(reusedInvestmentValue(scope,'currency',{...recovered,currency:'MZN'}),null);
assert.equal(reusedInvestmentValue(scope,'project_title',{...recovered,context_reuse:'invalid'}),null);
});
test('local geography and sector keep their labels when the dossier proposes different identifiers', () => {
const scope='dossier:A:simulator';
const source={province_id:'p1',province_id_label:'Sofala',district_id:'d1',district_id_label:'Dondo',sector_id:'3',sector_id_label:'Agronegócio'};
const local={province_id:'p2',province_id_label:'Gaza',district_id:'d2',district_id_label:'Outro distrito',sector_id:'4',sector_id_label:'Turismo'};
const reconciliation=inheritCompatibleFields(scope,local,source,null);
assert.deepEqual(reconciliation.patch,{});
assert.ok(reconciliation.conflicts.includes('province_id'));
const first=inheritCompatibleFields(scope,{},source,null);
const changed=inheritCompatibleFields(scope,{...first.patch,context_reuse:first.metadata},{...source,province_id:'p3',province_id_label:'Manica',district_id:'',district_id_label:''},first.metadata);
assert.equal(changed.patch.province_id,'p3');assert.equal(changed.patch.province_id_label,'Manica');
assert.equal(changed.patch.district_id,'');assert.equal(changed.patch.district_id_label,'');
const stale=inheritCompatibleFields(scope,{province_id_label:'Nome antigo'},source,null);
assert.equal(stale.patch.province_id,'p1');assert.equal(stale.patch.province_id_label,'Sofala');
});
test('investment entry selects the existing simulation route without replacing an earlier deliberate purpose', () => {
const scope='dossier:A:simulator';
const source={journey_type:'investor',journey_type_label:'Avaliar o meu investimento',purpose:'simulate_only',purpose_label:'Apenas simular'};
const first=inheritCompatibleFields(scope,{},source,null);
const values={...first.patch,context_reuse:first.metadata};
assert.equal(reusedInvestmentValue(scope,'journey_type',values),'Avaliar o meu investimento');
assert.equal(reusedInvestmentValue(scope,'purpose',values),'Apenas simular');
const old=inheritCompatibleFields(scope,{journey_type:'small_business',purpose:'seek_partner'},source,null);
assert.deepEqual(old.patch,{});
assert.equal(reusedInvestmentValue(scope,'purpose',{purpose:'seek_partner',context_reuse:old.metadata}),null);
});
test('inherited source updates follow through recovery while local edits and deliberate clears survive',()=>{
const first=inheritCompatibleFields('dossier:A:simulator',{}, {currency:'MZN',project_title:'A'}, null);
const saved={...first.patch,context_reuse:first.metadata};
@@ -0,0 +1,22 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { inheritCompatibleFields } from '../../resources/js/lib/context-inheritance.ts';
import { buildProjectDossierRows, exportProjectDossierCsv } from '../../resources/js/lib/project-dossier.ts';
import { createProjectDossierReport, createProjectDossierPrintHtml } from '../../resources/js/lib/project-dossier-report.ts';
test('reports distinguish opportunity inheritance, dossier declarations and later edits without rewriting the snapshot', async()=>{
const source={project_title:'Título do promotor',project_summary:'Resumo da oportunidade',sector_id:'1',sector_id_label:'Agronegócio',context_applicability_snapshot:JSON.stringify({version:1,field_provenance:{project_summary:{kind:'opportunity',title:'Florestas — Corredor da Beira',version:2},sector_id:{kind:'opportunity',title:'Florestas — Corredor da Beira',version:2},project_title:{kind:'process_declaration',context_revision:1}}})};
const inherited=inheritCompatibleFields('dossier:A:simulator',{},source,null);
const draft={...inherited.patch,context_reuse:inherited.metadata};
let rows=buildProjectDossierRows(draft);
assert.match(rows.find(r=>r[1]==='Resumo')[3],/Herdado de oportunidade «Florestas/);
assert.match(rows.find(r=>r[1]==='Título')[3],/dados introduzidos pelo utilizador no dossier/);
const edited={...draft,project_summary:'Resumo alterado no simulador'}; const before=JSON.stringify(edited);
rows=buildProjectDossierRows(edited);
assert.match(rows.find(r=>r[1]==='Resumo')[3],/Alterado posteriormente pelo utilizador; origem anterior: oportunidade/);
assert.match(exportProjectDossierCsv(edited),/origem anterior: oportunidade/);
const report=createProjectDossierReport(edited,{selectedSections:new Set(['summary','project'])});
assert.match(await createProjectDossierPrintHtml(report,[],false),/Resumo alterado no simulador/);
assert.equal(JSON.stringify(edited),before);
assert.equal(buildProjectDossierRows({project_summary:'Legado'}).find(r=>r[1]==='Resumo')[3],'Declarado pelo utilizador');
});
@@ -0,0 +1,54 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { investmentLandPreparation, landStatusVisible } from '../../resources/js/lib/investment-land-preparation.ts';
import { simulationFieldGroups, validateSimulationDraft, partitionSimulationDraft, restoreSimulationDraft } from '../../resources/js/lib/simulation-form.ts';
import { inheritCompatibleFields } from '../../resources/js/lib/context-inheritance.ts';
import { buildProjectDossierRows, exportProjectDossierCsv } from '../../resources/js/lib/project-dossier.ts';
import { createProjectDossierReport, createProjectDossierPrintHtml } from '../../resources/js/lib/project-dossier-report.ts';
const snapshot = (need) => JSON.stringify({version:1,context_revision:2,land_preparation:{need,reason:'Decisão guardada no dossier'}});
const draft = (need) => ({purpose:'simulate_only',journey_type:'investor',land_status:'duat',land_status_label:'DUAT',context_applicability_snapshot:snapshot(need)});
test('no hides land status and yes restores the same saved value; unknown is a pending decision without a required answer', () => {
const field=simulationFieldGroups.location.find(field=>field.key==='land_status');
for(const need of ['no','yes','unknown']) {
const input=draft(need);const before=JSON.stringify(input);
assert.equal(field.showWhen(input),need==='yes');
assert.equal(Boolean(validateSimulationDraft({...input,land_status:''},'location').land_status),false);
const saved=partitionSimulationDraft(input);const recovered=restoreSimulationDraft(saved.personal_snapshot,saved.economic_snapshot);
assert.equal(recovered.land_status,'duat');assert.equal(recovered.land_status_label,'DUAT');
assert.equal(investmentLandPreparation(recovered.context_applicability_snapshot).need,need);
assert.equal(JSON.stringify(input),before);
}
});
test('old snapshots use S10 and absent or malformed decisions never imply yes or no', () => {
for(const [applicability,need] of [['applicable','yes'],['not_needed_for_preparation','no'],['undetermined','unknown']])
assert.equal(investmentLandPreparation(JSON.stringify({version:1,path:{steps:[{code:'S10',applicability}]}})).need,need);
for(const raw of ['', 'invalid', '{}', '{"version":1,"land_preparation":{"need":"invalid"}}'])
assert.equal(investmentLandPreparation(raw).need,'unknown');
assert.equal(landStatusVisible({}),true); // Standalone simulator preserves its previous behaviour.
});
test('current dossier assessment refreshes after server recovery without overwriting the saved land answer', () => {
const scope='dossier:A:simulator';
const inherited=inheritCompatibleFields(scope,{}, {context_applicability_snapshot:snapshot('yes'),land_status:'duat'},null);
const recovered={...inherited.patch,context_reuse:inherited.metadata,context_applicability_snapshot:snapshot('no')};
const restored=inheritCompatibleFields(scope,recovered,{context_applicability_snapshot:snapshot('yes')},recovered.context_reuse);
assert.equal(restored.patch.context_applicability_snapshot,snapshot('yes'));
assert.equal(Object.hasOwn(restored.patch,'land_status'),false);
});
test('report, CSV and print reflect yes, no and unknown without treating historical DUAT as current when hidden', async () => {
for(const need of ['yes','no','unknown']) {
const input=draft(need);const before=JSON.stringify(input);
const rows=buildProjectDossierRows(input);
assert.equal(rows.some(row=>row[1]==='Situação do terreno'),need==='yes');
const report=createProjectDossierReport(input,{selectedSections:new Set(['location'])});
const print=await createProjectDossierPrintHtml(report,[],false);
const csv=exportProjectDossierCsv(input);
if(need==='yes'){assert.match(print,/DUAT/);assert.match(csv,/DUAT/);}
else {assert.doesNotMatch(print,/DUAT/);assert.doesNotMatch(csv,/DUAT/);assert.match(print,need==='no'?/valores anteriores conservados/:/Definir se precisa de terra/);}
assert.equal(JSON.stringify(input),before);
}
});
@@ -0,0 +1,25 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { inheritCompatibleFields } from '../../resources/js/lib/context-inheritance.ts';
import { buildProjectDossierRows, exportProjectDossierCsv } from '../../resources/js/lib/project-dossier.ts';
import { createProjectDossierReport, createProjectDossierPrintHtml } from '../../resources/js/lib/project-dossier-report.ts';
test('selected associations stay attached to each opportunity in reports and do not replace deliberate simulator answers', async()=>{
const choices=[
{opportunity:{key:'opportunity:1',title:'Oportunidade QA A'},territory:{title:'Sofala / Dondo'},portfolio_origins:[{title:'Âncora QA',id:'p',sha256:'internal-hash'}]},
{opportunity:{key:'opportunity:2',title:'Oportunidade QA B'},territory:{title:'Manica'},portfolio_origins:[{title:'Cluster QA',id:'c'}]},
];
const snapshot=JSON.stringify({version:1,context_revision:3,opportunity_choices:choices,land_preparation:{need:'unknown'}});
const values={project_title:'Título deliberado',province_id:'local-edit',context_applicability_snapshot:'old'};
const result=inheritCompatibleFields('dossier:A:simulator',values,{project_title:'Origem',province_id:'source',context_applicability_snapshot:snapshot},null);
assert.equal(result.patch.project_title,undefined);assert.equal(result.patch.province_id,undefined);
assert.equal(result.patch.context_applicability_snapshot,snapshot);
const draft={...values,...result.patch};const before=JSON.stringify(draft);
const rows=buildProjectDossierRows(draft);
assert.ok(rows.some(row=>row[1]==='Associação escolhida na carteira'&&row[2]==='Âncora QA'&&row[3].startsWith('Oportunidade QA A;')));
assert.ok(rows.some(row=>row[1]==='Associação escolhida na carteira'&&row[2]==='Cluster QA'&&row[3].startsWith('Oportunidade QA B;')));
assert.match(exportProjectDossierCsv(draft),/Sofala \/ Dondo/);
const html=await createProjectDossierPrintHtml(createProjectDossierReport(draft,{selectedSections:new Set(['location'])}),[],false);
assert.match(html,/Âncora QA/);assert.match(html,/Cluster QA/);assert.match(html,/Manica/);
assert.doesNotMatch(html,/internal-hash/);assert.equal(JSON.stringify(draft),before);
});
@@ -12,6 +12,29 @@ import {
validateDossierAttachments,
} from '../../resources/js/lib/project-dossier-report.ts';
test('report separates inapplicable documents from missing and unknown requirements without changing the saved snapshot', async () => {
const requirements = [
{ label: 'Título DUAT', state: 'not_applicable', reason: 'Arrendamento declarado' },
{ label: 'Identificação', state: 'missing', reason: 'Pedido em preparação' },
{ label: 'Capital externo', state: 'indeterminate', reason: 'Origem por determinar' },
];
const snapshot = JSON.stringify({ version: 1, context_revision: 2, preparation: {
enabled: true, mode: 'registration', version: 'QA', next: 'Completar identificação',
calculation_notice: 'Cálculo separado', notice: 'Preparação', requirements,
} });
const draft = { context_applicability_snapshot: snapshot };
const report = createProjectDossierReport(draft, { selectedSections: new Set(['sources']) });
const section = report.sections.find((section) => section.id === 'sources');
assert.ok(section);
assert.equal(section.rows.some((row) => row.field === 'Título DUAT'), false);
assert.match(section.paragraphs.join(' '), /Não exigido neste contexto: Título DUAT \(Arrendamento declarado\)/);
assert.equal(section.rows.find((row) => row.field === 'Identificação').value, 'Documento por associar');
assert.equal(section.rows.find((row) => row.field === 'Capital externo').value, 'Informação em falta para decidir');
const print = await createProjectDossierPrintHtml(report, [], false);
assert.match(print, /Arrendamento declarado/);
assert.equal(draft.context_applicability_snapshot, snapshot);
});
test('executive summary exposes the same financial result and period without replacing missing data by zero', () => {
const draft = { currency:'MZN', financial_tools_snapshot:serializeFinancialToolsSnapshot({budgetPeriod:'Mensal',loan:{principal:'1200',rate:'0',months:'12',rateType:'nominal',periodMonths:'1',gracePeriods:'0',upfrontFee:'0',recurringFee:'0'},breakEven:{fixed:'100.01',price:'30',variable:'20'},funding:{total:'1000',secured:'800'},incomes:[{label:'Receita QA',amount:'1000'}],expenses:[{label:'Despesa QA',amount:'1100'}]}) };
const report = createProjectDossierReport(draft,{selectedSections:new Set(['summary','finance'])});
+10 -1
View File
@@ -1,6 +1,15 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { searchTerritoryHref } from '../../resources/js/lib/search-territory.ts';
import { searchTerritoryHref, investmentPreparationHref } from '../../resources/js/lib/search-territory.ts';
test('preparation carries only the validated portfolio origin and territorial choices, including nested catalogue return context',()=>{
const href=investmentPreparationHref(53,'/oportunidades/florestas?province_id=p&district_id=d&q=private-note&context=other','origin');
const query=new URL(href,'https://portal.invalid').searchParams;
assert.equal(query.get('source'),'opportunity:53');assert.equal(query.get('from_dossier'),'origin');
assert.equal(query.get('province_id'),'p');assert.equal(query.get('district_id'),'d');
assert.equal(query.has('q'),false);assert.equal(query.has('context'),false);
assert.equal(new URL(investmentPreparationHref(53,'/oportunidades/florestas?catalogue_query='+encodeURIComponent('province_id=p&district_id=d')),'https://portal.invalid').searchParams.get('district_id'),'d');
});
test('portfolio to catalogue to related screen retains only territorial search filters', () => {
const start = '/carteira?province_id=inh&district_id=vil&q=airport&kind=project';
@@ -0,0 +1,12 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { simulationDraftEtag } from '../../resources/js/lib/simulation-draft-etag.ts';
test('compressed draft reads use the exact version token while genuinely stale versions stay different', () => {
const tag='"sfd-6c07a968-7ff9-42fa-b252-072bb1acf221-v2"';
assert.equal(simulationDraftEtag(`W/${tag}`),tag);
assert.equal(simulationDraftEtag(tag),tag);
assert.notEqual(simulationDraftEtag(`W/${tag}`),tag.replace('-v2','-v3'));
assert.equal(simulationDraftEtag('W/"another-resource"'),'W/"another-resource"');
assert.equal(simulationDraftEtag(null),null);
});
+23
View File
@@ -0,0 +1,23 @@
<?php
// Local QA entry, ordinary login/MFA; never maps to homologation or the active DB.
foreach (['APP_ENV' => 'testing', 'APP_URL' => 'http://127.0.0.1:18574', 'DB_CONNECTION' => 'pgsql', 'DB_HOST' => '127.0.0.1',
'DB_PORT' => '55441', 'DB_DATABASE' => 'simulator_fiscal_delta_20261021_test', 'DB_USERNAME' => 'postgres', 'DB_PASSWORD' => '', 'DB_URL' => '',
'PORTAL_PROFILE_ACCESS_ENABLED' => 'true', 'PORTAL_OPPORTUNITY_SPECIALIZED_DRAFTS_ENABLED' => 'true',
'INVESTOR_PATH_ENABLED' => 'true', 'SESSION_DRIVER' => 'file', 'SESSION_COOKIE' => 'agribusiness_opportunities_qa',
'CACHE_STORE' => 'array', 'MAIL_MAILER' => 'array', 'QUEUE_CONNECTION' => 'sync'] as $k => $v) {
putenv($k.'='.$v); $_ENV[$k] = $_SERVER[$k] = $v;
}
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$public = realpath(__DIR__.'/../public');
$asset = realpath($public.rawurldecode($path));
if ($asset && str_starts_with($asset, $public.DIRECTORY_SEPARATOR) && is_file($asset) && pathinfo($asset, PATHINFO_EXTENSION) !== 'php') return false;
define('LARAVEL_START', microtime(true));
require __DIR__.'/../vendor/autoload.php';
$app = require __DIR__.'/../bootstrap/app.php';
$app->make(Illuminate\Contracts\Http\Kernel::class)->bootstrap();
$target = Illuminate\Support\Facades\DB::selectOne('SELECT current_database() AS name, inet_server_port() AS port');
if (! $app->environment('testing') || $target->name !== 'simulator_fiscal_delta_20261021_test' || (int) $target->port !== 55441) {
http_response_code(503); exit('QA target rejected');
}
$app->handleRequest(Illuminate\Http\Request::capture());
+19
View File
@@ -119,6 +119,13 @@ if editorial_receipt.exists():
editorial_rows = {int(re.search(r'post-(\d+)-', r['external_id'])[1]): r for r in editorial['rows']}
assert len(editorial_rows) == 28 and all(r['private'] for r in editorial_rows.values())
extra = ['Endereço no novo portal', 'Estado actual consolidado', 'Pendência actual', 'Conferência HTTP UTC', 'Grupo de preparação QA']
destination_receipt = ROOT / 'docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json'
destination_rows = {}
if destination_receipt.exists():
destination = json.loads(destination_receipt.read_text(encoding='utf-8'))
assert destination['new_publications'] == 0
destination_rows = {r['legacy_id']: r for r in destination['rows']}
assert len(destination_rows) == 28 and all(r['private'] for r in destination_rows.values())
for col in extra:
if col not in columns: columns.append(col)
articles = {a['legacy_id']: a for a in reconciliation['articles']}
@@ -157,6 +164,18 @@ for row in rows:
row['Dependência concreta'] = row['Pendência actual']
row['Próxima ação'] = 'Seguir o guia e a sequência de entrega privada do manual; não repetir importação QA ou provas dos 20 aceites.'
row['Evidência atual'] = 'legado-editorial-qa-20261022.json; manual: Legado — edição e leitura dos 28 rascunhos em QA; inventário e referências históricas preservados'
if legacy_id in destination_rows:
item = destination_rows[legacy_id]
assert item['sha256'] == prepared[legacy_id]['sha256']
row['Endereço no novo portal'] = f"https://teste-investidor.tempo.co.mz/management/portal-services/resources/{item['resource_id']}/article/preview"
row['Recurso ativo'] = str(item['resource_id'])
row['Estado actual consolidado'] = 'Rascunho privado na homologação; pesquisa, editor, prévia e recuperação conferidos'
row['Pendência actual'] = 'Revisão editorial e publicação separada pela equipa no portal. As referências sem exemplar de imagem verificado permanecem explícitas.'
if legacy_id == 62413:
row['Pendência actual'] += ' Rever/corrigir a nota de demonstração QA antes de publicar.'
row['Dependência concreta'] = row['Pendência actual']
row['Próxima ação'] = 'Rever pela gestão; corrigir e pré-visualizar; publicar expressamente quando autorizado, sem comandos.'
row['Evidência atual'] = 'docs/investmoz/unification/legacy-editorial-installation-receipt-20261022.json; preservação dos registos anteriores conferida; provas QA reutilizadas'
assert len(rows) == 50 and len({row['ID legado'] for row in rows}) == 50
with path.open('w', encoding='utf-8-sig', newline='') as f:
writer = csv.DictWriter(f, columns); writer.writeheader(); writer.writerows(rows)
@@ -0,0 +1,45 @@
<?php
foreach (['APP_ENV' => 'testing', 'DB_CONNECTION' => 'pgsql', 'DB_HOST' => '127.0.0.1', 'DB_PORT' => '55441',
'DB_DATABASE' => 'simulator_fiscal_delta_20261021_test', 'DB_USERNAME' => 'postgres', 'DB_PASSWORD' => '', 'DB_URL' => '', 'SESSION_DRIVER' => 'array'] as $k => $v) {
putenv($k.'='.$v); $_ENV[$k] = $_SERVER[$k] = $v;
}
require __DIR__.'/../vendor/autoload.php';
$app = require __DIR__.'/../bootstrap/app.php';
$app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();
use Illuminate\Support\Facades\DB;
use App\Models\Opportunity;
use App\Services\CorridorAgribusinessOpportunities;
use App\Services\OpportunityPromotion;
$target = DB::selectOne('SELECT current_database() AS name, inet_server_port() AS port');
if (! app()->environment('testing') || $target->name !== 'simulator_fiscal_delta_20261021_test' || (int) $target->port !== 55441) throw new RuntimeException('QA only');
config(['portal.profile_access_enabled' => true]);
$actor = App\Models\User::findOrFail(295);
// QA-role simulation, like the bounded tests; this is not evidence of a real production MFA login.
session(['portal.mfa_actor' => $actor->id, 'portal.mfa_at' => time(), 'login.id' => null]);
$request = Illuminate\Http\Request::create('/minhas-oportunidades', 'GET');
$request->setUserResolver(fn () => $actor); $app->instance('request', $request);
$entries = app(CorridorAgribusinessOpportunities::class)->entries();
$dir = storage_path('app/private/agribusiness-opportunities-20261023');
$records = Opportunity::whereIn('management_creation_key', array_column($entries, 'key'))->get();
if (! file_exists($dir.'/before-disclosure.json')) file_put_contents($dir.'/before-disclosure.json', json_encode($records->map(fn ($r) => $r->getRawOriginal())->all(), JSON_THROW_ON_ERROR));
$result = DB::transaction(function () use ($actor, $entries) {
DB::select('SELECT pg_advisory_xact_lock(231026, 34)');
$result = [];
foreach ($entries as $entry) {
$record = Opportunity::where('management_creation_key', $entry['key'])->lockForUpdate()->firstOrFail();
if ($record->status !== 'published') {
$values = app(App\Services\OpportunityManagement::class)->values($record);
if ($record->status !== 'draft' || ($values['canonical']['official_title'] ?? null) !== $entry['title'] || ($values['canonical']['executive_summary'] ?? null) !== $entry['summary']) throw new RuntimeException('Edited or withdrawn record requires individual review: '.$record->id);
$input = $record->workflow_context['promotional_summary']['input'];
$preview = app(OpportunityPromotion::class)->preview($actor, $record, [...$input, 'source_date' => null, 'action' => 'disclose', 'etag' => app(App\Services\OpportunityManagement::class)->etag($record)]);
app(OpportunityPromotion::class)->confirm($actor, $record->id, ['token' => $preview['token'], 'operation_key' => (string) Illuminate\Support\Str::uuid(), 'confirm' => true,
'reason' => 'QA automatizada: divulgação das 34 fichas de Agronegócio expressamente instruída pela Tempo; em preparação, sem autorização de execução.']);
} elseif (data_get($record->workflow_context, 'publication.purpose') !== OpportunityPromotion::PURPOSE) throw new RuntimeException('Different publication preserved');
$result[] = ['id' => $record->id, 'title' => $entry['title'], 'corridor' => $entry['corridor'], 'url' => '/oportunidades/'.$record->slug];
}
return $result;
});
$receipt = ['target' => $target, 'published' => count($result), 'authentication' => 'QA role simulation only; no production authentication claimed', 'source_sha256' => CorridorAgribusinessOpportunities::SOURCE_HASH, 'records' => $result, 'active_writes' => 0];
file_put_contents($dir.'/disclosure-receipt.json', json_encode($receipt, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
echo json_encode(['published' => count($result), 'target' => $target, 'active_writes' => 0]).PHP_EOL;
@@ -0,0 +1,34 @@
<?php
// Initial source loading only. Everyday editing and disclosure use the portal.
$options = getopt('', ['apply', 'actor:', 'organization:']);
foreach (['APP_ENV' => 'testing', 'DB_CONNECTION' => 'pgsql', 'DB_HOST' => '127.0.0.1', 'DB_PORT' => '55441',
'DB_DATABASE' => 'simulator_fiscal_delta_20261021_test', 'DB_USERNAME' => 'postgres', 'DB_PASSWORD' => '', 'DB_URL' => ''] as $k => $v) {
putenv($k.'='.$v); $_ENV[$k] = $_SERVER[$k] = $v;
}
require __DIR__.'/../vendor/autoload.php';
$app = require __DIR__.'/../bootstrap/app.php';
$app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();
use Illuminate\Support\Facades\DB;
use App\Services\CorridorAgribusinessOpportunities;
$target = DB::selectOne('SELECT current_database() AS name, inet_server_port() AS port');
if (! app()->environment('testing') || $target->name !== 'simulator_fiscal_delta_20261021_test' || (int) $target->port !== 55441) throw new RuntimeException('Refusing non-QA target.');
config(['portal.profile_access_enabled' => true, 'portal.opportunity_specialized_drafts_enabled' => true]);
$service = app(CorridorAgribusinessOpportunities::class);
if (! isset($options['apply'])) {echo json_encode(['target' => $target, 'entries' => $service->entries(), 'writes' => 0], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); exit;}
$actor = App\Models\User::findOrFail($options['actor'] ?? 0);
$organization = $options['organization'] ?? '';
$request = Illuminate\Http\Request::create('/minhas-oportunidades/drafts', 'POST');
$request->setUserResolver(fn () => $actor); $app->instance('request', $request);
$dir = storage_path('app/private/agribusiness-opportunities-20261023');
if (! is_dir($dir)) mkdir($dir, 0700, true);
if (! file_exists($dir.'/before.json')) {
$before = [];
foreach (['opportunities', 'audit_events', 'development_corridors', 'portal_role_assignments', 'portal_resources', 'documents'] as $table) $before[$table] = DB::table($table)->orderBy('id')->get()->all();
file_put_contents($dir.'/before.json', json_encode($before, JSON_THROW_ON_ERROR));
}
$records = $service->register($actor, $organization);
$receipt = ['target' => $target, 'source_sha256' => $service::SOURCE_HASH, 'records' => $records, 'total' => count($records),
'new' => count(array_filter($records, fn ($r) => $r['created'])), 'published' => App\Models\Opportunity::whereIn('id', array_column($records, 'id'))->published()->count(), 'active_writes' => 0];
file_put_contents($dir.'/receipt.json', json_encode($receipt, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
echo json_encode($receipt, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE).PHP_EOL;
@@ -0,0 +1,96 @@
<?php
// Delimited recovery requested by Tempo, 23/10/2026. Never import or approve new/edited dossiers.
declare(strict_types=1);
ini_set('zend.exception_ignore_args', '1');
require getcwd().'/vendor/autoload.php';
$app = require getcwd().'/bootstrap/app.php';
(new Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables)->bootstrap($app);
(new Illuminate\Foundation\Bootstrap\LoadConfiguration)->bootstrap($app);
$args = getopt('', ['manifest:', 'output:', 'apply', 'preview-sha256:', 'backup:', 'qa']);
$manifestPath = $args['manifest'] ?? '';
$manifestHash = '8d07007a2e1e8758d4741b4ac594f78d5943f71ad78b2daae7f85aefa9d2de4a';
if (!is_file($manifestPath) || !hash_equals($manifestHash, hash_file('sha256', $manifestPath))) {
throw new RuntimeException('Exact reviewed 29-original recovery manifest required.');
}
$manifest = json_decode(file_get_contents($manifestPath), true, 512, JSON_THROW_ON_ERROR);
if (count($manifest['items']) !== 29 || count(array_unique(array_column($manifest['items'], 'target_record_id'))) !== 29) {
throw new RuntimeException('Exact original scope required.');
}
$qa = isset($args['qa']);
$c = config('database.connections.'.config('database.default'));
$expected = $qa ? 'simulator_fiscal_delta_20261021_test' : 'teste_investidor';
$port = $qa ? 55441 : 5432;
if ($c['driver'] !== 'pgsql' || !empty($c['url']) || !in_array($c['host'], ['127.0.0.1', 'localhost'], true) || $c['database'] !== $expected || (int)$c['port'] !== $port) {
throw new RuntimeException('Named target mismatch.');
}
if (!$qa && (!getenv('SSH_CONNECTION') || !function_exists('posix_geteuid') || posix_geteuid() !== 0 || rtrim(config('app.url'), '/') !== 'https://teste-investidor.tempo.co.mz')) {
throw new RuntimeException('Authenticated destination operator required.');
}
$output = $args['output'] ?? '';
if (!$output || str_contains(str_replace('\\', '/', $output), '/public/')) {
throw new RuntimeException('Private receipt path required.');
}
$pdo = new PDO('pgsql:host='.$c['host'].';port='.$c['port'].';dbname='.$expected, $c['username'], $c['password'], [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$apply = isset($args['apply']);
$pdo->exec($apply ? 'BEGIN ISOLATION LEVEL SERIALIZABLE' : 'BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY');
try {
$actual = $pdo->query('select current_database() as name,inet_server_port() as port')->fetch(PDO::FETCH_ASSOC);
if ($actual['name'] !== $expected || (int)$actual['port'] !== $port) throw new RuntimeException('Actual target mismatch.');
if ($apply) $pdo->query('select pg_advisory_xact_lock(231026,29)');
$plan = [];
$before = [];
foreach ($manifest['items'] as $item) {
$query = $pdo->prepare('select * from investment_dossiers where id = ?'.($apply ? ' for update' : ''));
$query->execute([$item['target_record_id']]);
$row = $query->fetch(PDO::FETCH_ASSOC);
if (!$row || $row['deleted_at'] !== null || $row['title'] !== $item['title'] || !hash_equals($item['content_sha256'], hash('sha256', $row['content'])) || !hash_equals($item['source_sha256'], hash('sha256', $row['source']))) {
throw new RuntimeException('Missing, archived or changed original: '.$item['code']);
}
if (!hash_equals($item['approval_sha256'], hash('sha256', $item['approval_evidence']))) throw new RuntimeException('Approval provenance changed.');
$replayed = $pdo->prepare("select count(*) from audit_events where source_system='portfolio-visibility-recovery-v1' and external_id=? and entity_id=? and action='approved_visibility_restored'");
$replayed->execute([$manifestHash.':'.$row['id'], $row['id']]);
$already = (int)$replayed->fetchColumn() === 1;
if ($already) {
if ($row['publication_status'] !== 'published' || $row['approval_basis'] !== 'document_owner' || $row['approval_evidence'] !== $item['approval_evidence']) throw new RuntimeException('Recovery subsequently changed: '.$item['code']);
} elseif ($row['publication_status'] !== 'draft' || $row['editor_id'] !== null || $row['reviewer_id'] !== null || (int)$row['version_no'] !== (int)$item['expected_target_version']) {
throw new RuntimeException('Preserve subsequent editorial decisions: '.$item['code']);
}
$before[] = $row;
$plan[] = ['id' => $row['id'], 'code' => $item['code'], 'version' => (int)$row['version_no'], 'status' => $row['publication_status'], 'content_sha256' => $item['content_sha256'], 'source_sha256' => $item['source_sha256'], 'already_restored' => $already];
}
$previewHash = hash('sha256', json_encode([$expected, $manifestHash, $plan], JSON_THROW_ON_ERROR));
if ($apply) {
if (!hash_equals($previewHash, $args['preview-sha256'] ?? '')) throw new RuntimeException('Preview changed; recover current state before writing.');
if (!$qa) {
$directory = $args['backup'] ?? '';
$backup = json_decode(file_get_contents($directory.'/backup-receipt.json'), true, 512, JSON_THROW_ON_ERROR);
$restore = json_decode(file_get_contents($directory.'/restore-receipt.json'), true, 512, JSON_THROW_ON_ERROR);
if (!$restore['restore_verified'] || $backup['target']['database'] !== $expected || (int)$backup['target']['port'] !== $port || !hash_equals($backup['sha256'], $restore['source_dump_sha256'])) throw new RuntimeException('Verified matching backup/restore required.');
}
$snapshot = $output.'.before.json';
if (is_file($snapshot)) throw new RuntimeException('Preserve earlier recovery snapshot; use a new receipt path.');
file_put_contents($snapshot, json_encode(['database' => $expected, 'rows' => $before], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR));
if (!$qa) chmod($snapshot, 0600);
foreach ($plan as $index => $entry) {
if ($entry['already_restored']) continue;
$item = $manifest['items'][$index];
$update = $pdo->prepare("update investment_dossiers set publication_status='published',approval_basis='document_owner',approval_evidence=?,published_at=now(),version_no=version_no+1,updated_at=now() where id=? and version_no=?");
$update->execute([$item['approval_evidence'], $entry['id'], $entry['version']]);
if ($update->rowCount() !== 1) throw new RuntimeException('Concurrent change.');
$event = $pdo->prepare("insert into audit_events(source_system,external_id,entity_type,entity_id,actor_user_id,action,before_data,after_data) values('portfolio-visibility-recovery-v1',?,'investment_dossiers',?,NULL,'approved_visibility_restored',?,?)");
$event->execute([$manifestHash.':'.$entry['id'], $entry['id'], json_encode(['version_no' => $entry['version'], 'publication_status' => 'draft', 'approval_basis' => $before[$index]['approval_basis']], JSON_THROW_ON_ERROR), json_encode(['code' => $item['code'], 'version_no' => $entry['version']+1, 'publication_status' => 'published', 'approval_basis' => 'document_owner', 'approval_sha256' => $item['approval_sha256'], 'content_sha256' => $item['content_sha256'], 'source_record_id' => $item['source_record_id'], 'source_published_at' => $item['original_published_at'], 'instruction' => 'Tempo 23/10/2026: preserve and restore completed approved functionality', 'authenticated_transport' => $qa ? 'isolated-QA' : 'SSH', 'not_a_new_editorial_approval' => true], JSON_THROW_ON_ERROR)]);
}
}
$visible = (int)$pdo->query("select count(*) from investment_dossiers d where publication_status='published' and (exists(select 1 from projects p where p.id=d.project_id and p.deleted_at is null) or exists(select 1 from economic_clusters c where c.id=d.cluster_id and c.deleted_at is null) or exists(select 1 from development_corridors r where r.id=d.corridor_id and r.deleted_at is null))")->fetchColumn();
$result = ['database' => $actual, 'qa' => $qa, 'mode' => $apply ? 'applied' : 'preview', 'manifest_sha256' => $manifestHash, 'preview_sha256' => $previewHash, 'scope' => count($plan), 'restored' => $apply ? count(array_filter($plan, fn($r) => !$r['already_restored'])) : 0, 'visible' => $visible, 'data_content_changed' => false, 'new_dossiers_imported' => 0, 'fake_editor_or_reviewer_assigned' => false, 'plan' => $plan];
if ($apply) $pdo->commit(); else $pdo->rollBack();
file_put_contents($output, json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR));
if (!$qa) chmod($output, 0600);
unset($result['plan']);
echo json_encode($result, JSON_THROW_ON_ERROR);
} catch (Throwable $error) {
if ($pdo->inTransaction()) $pdo->rollBack();
throw $error;
}
+33
View File
@@ -0,0 +1,33 @@
<?php
require __DIR__.'/../vendor/autoload.php';
// Offline verification against the preserved article exemplar; no database or source writes.
$path = $argv[1] ?? throw new RuntimeException('Pass the preserved agronegocio article JSON path.');
$sourceHash = hash_file('sha256', $path);
$source = json_decode(file_get_contents($path), true, 512, JSON_THROW_ON_ERROR);
if (($source['format'] ?? '') !== 'investmoz-legacy-article-v1' || ($source['slug'] ?? '') !== 'agronegocio') {
throw new RuntimeException('Unexpected source article.');
}
$original = (new App\Services\LegacyContent)->sanitize($source['content_html']);
$html = (new App\Services\CorridorAgribusiness)->enrich($original);
$doc = new DOMDocument;
$doc->loadHTML('<?xml encoding="UTF-8">'.$html);
$xpath = new DOMXPath($doc);
$groups = $xpath->query('//ul[starts-with(@id,"agronegocio-oportunidades-")]');
$entries = $xpath->query('//ul[starts-with(@id,"agronegocio-oportunidades-")]/li');
if ($groups->length !== 6 || $entries->length !== 34 || $sourceHash !== hash_file('sha256', $path)) {
throw new RuntimeException('Incomplete integration or source changed.');
}
// Removing only the additions must recover the exact sanitized original.
foreach (iterator_to_array($groups) as $group) {
$group->parentNode->removeChild($group->previousSibling);
$group->parentNode->removeChild($group->nextSibling);
$group->parentNode->removeChild($group);
}
$body = $doc->getElementsByTagName('body')->item(0);
$recovered = implode('', array_map(fn ($node) => $doc->saveHTML($node), iterator_to_array($body->childNodes)));
if ($recovered !== $original) {
throw new RuntimeException('Original displayed content was changed.');
}
echo json_encode(['source_sha256' => $sourceHash, 'source_unchanged' => true, 'original_display_preserved' => true,
'corridors' => 6, 'agribusiness_entries' => 34, 'database_writes' => 0], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE).PHP_EOL;