Compare commits
66
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4098192a05 | ||
|
|
6b544548d3 | ||
|
|
c3f7c3ad10 | ||
|
|
445aa7e69d | ||
|
|
7c1f532134 | ||
|
|
115c773162 | ||
|
|
3d877ec745 | ||
|
|
6948ed62fc | ||
|
|
7d798579a6 | ||
|
|
5bda74aedc | ||
|
|
f2150b1912 | ||
|
|
7ca94967f3 | ||
|
|
e1f010f941 | ||
|
|
0025309b98 | ||
|
|
a0e80db4ec | ||
|
|
7226287526 | ||
|
|
f1ac82ae4d | ||
|
|
98474777f6 | ||
|
|
3848f29bea | ||
|
|
203492997e | ||
|
|
165c2f6171 | ||
|
|
4ad1bf402b | ||
|
|
e12ef6ad42 | ||
|
|
c641b267ac | ||
|
|
7d08c2c664 | ||
|
|
761035aefb | ||
|
|
20a4099d10 | ||
|
|
e03de682ca | ||
|
|
37a8eb015a | ||
|
|
65eddf6bc7 | ||
|
|
fb4e7d8970 | ||
|
|
bc93ccdb9e | ||
|
|
7ccf62adec | ||
|
|
f4d0970917 | ||
|
|
e46f973159 | ||
|
|
1bcf34c0c0 | ||
|
|
ef0c2acd36 | ||
|
|
30aed7cd12 | ||
|
|
95d4cf44d7 | ||
|
|
73c046e104 | ||
|
|
15d82611b6 | ||
|
|
8286d7504d | ||
|
|
aa2b3d05b0 | ||
|
|
97d51db60a | ||
|
|
a1b553372e | ||
|
|
f80d30f478 | ||
|
|
916819eb35 | ||
|
|
1d921c9d03 | ||
|
|
7b1c35793c | ||
|
|
1ec318561c | ||
|
|
9ac623a433 | ||
|
|
5f4eb5cfaa | ||
|
|
042ffbde44 | ||
|
|
064296e1fb | ||
|
|
a1fbda06ad | ||
|
|
52fbb1468a | ||
|
|
a76369eb86 | ||
|
|
57fc914821 | ||
|
|
0b0cf76137 | ||
|
|
3f5430c3ce | ||
|
|
f3ec686f8c | ||
|
|
d3fe8a02ec | ||
|
|
f5b1d67ee5 | ||
|
|
90179149c7 | ||
|
|
364be835fe | ||
|
|
480effa5fc |
@@ -0,0 +1,16 @@
|
||||
.git
|
||||
.idea
|
||||
.agents
|
||||
.codex
|
||||
node_modules
|
||||
vendor
|
||||
public/build
|
||||
storage/logs
|
||||
storage/framework/cache
|
||||
storage/framework/sessions
|
||||
storage/framework/views
|
||||
tests
|
||||
Doc
|
||||
docs
|
||||
*.zip
|
||||
.phpunit.result.cache
|
||||
@@ -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.
|
||||
@@ -39,3 +47,20 @@ Referenciar estas orientações nos requisitos correspondentes da matriz existen
|
||||
- Preservar o ficheiro original, a proveniência, o identificador exacto, a edição/data, o hash e o histórico. Não marcar autenticidade, vigência, direitos ou scan como confirmados sem evidência.
|
||||
- Para fontes fiscais, inventariar também as fontes não analisadas e os diplomas/artigos sem correspondência documental. Parâmetros ausentes ou ambiguidades permanecem indeterminados; não usar zero nem a taxa mais recente como substituto.
|
||||
- Antes de aplicar mudanças em dados activos, confirmar backup/restauro e a acção autenticada que continua necessária. Testes e migrações devem usar exclusivamente uma base QA isolada com guarda contra a base activa.
|
||||
|
||||
|
||||
## Execução das fontes e decisões — orientação permanente da Tempo, 22/10/2026
|
||||
|
||||
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.
|
||||
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
+342
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -40,7 +40,7 @@ class SimulationController extends Controller
|
||||
$organizationId = $validated['organization_id'] ?? null;
|
||||
$execution->authorizeOrganization($actorId, $organizationId);
|
||||
if (($validated['scenario_kind'] ?? null) === 'fiscal_rule_published') {
|
||||
$input = array_intersect_key($validated, array_flip(['base', 'currency', 'reference_date', 'operation', 'scope_declared']));
|
||||
$input = array_intersect_key($validated, array_flip(['base', 'currency', 'reference_date', 'operation', 'scope_declared', 'monthly_sales', 'sales_vat_basis']));
|
||||
$input['scope_declared'] = (bool) $input['scope_declared'];
|
||||
$result = $execution->createPublishedFiscal($input, $actorId, $organizationId, $token, $key);
|
||||
} else {
|
||||
|
||||
@@ -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];
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -81,31 +81,18 @@ class EditorialArticleController extends Controller
|
||||
app(\App\Services\DocumentManagementAccess::class)->assertEditableResource(request()->user(), $id);
|
||||
$r = DB::table('portal_resources')->find($id);
|
||||
abort_unless($r, 404);
|
||||
$content = $r->editorial_content ? json_decode($r->editorial_content, true) : null;
|
||||
if ($content === null) {
|
||||
$document = DB::table('documents')->find($r->document_id);
|
||||
if ($document && $document->status === 'active' && $document->malware_scan_status === 'clean'
|
||||
&& in_array($document->media_type, ['text/plain', 'application/json'], true)) {
|
||||
$binary = app(VerifiedDocumentBinary::class)->respond($document);
|
||||
$bytes = $binary instanceof \Symfony\Component\HttpFoundation\BinaryFileResponse
|
||||
? file_get_contents($binary->getFile()->getPathname()) : $binary->getContent();
|
||||
$original = json_decode($bytes, true);
|
||||
if (is_array($original) && ($original['format'] ?? '') === 'investmoz-legacy-article-v1') {
|
||||
$html = preg_replace('~</(?:p|div|li|h[1-6])>|<br\s*/?>~i', "\n\n", $original['content_html'] ?? '');
|
||||
$content = ['body' => trim(html_entity_decode(strip_tags($html), ENT_QUOTES | ENT_HTML5, 'UTF-8')), 'image_resource_id' => null];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Inertia::render('portal/article-editor', ['resource' => ['id' => $r->id, 'title' => $r->title, 'metadata_version' => $r->metadata_version],
|
||||
'content' => $content, 'images' => $this->publicResources()->whereIn('d.media_type', ['image/png', 'image/jpeg', 'image/webp'])->get(['r.id', 'r.title']),
|
||||
'revisions' => DB::table('portal_resource_revisions')->where('resource_id', $id)->orderByDesc('revision_no')->get(['revision_no', 'change_type', 'created_at'])]);
|
||||
$reading = app(\App\Services\EditorialReading::class);
|
||||
$original = $reading->original($r);
|
||||
$content = $reading->content($r, $original);
|
||||
return Inertia::render('portal/article-editor', ['resource' => ['id' => $r->id, 'title' => $r->title, 'metadata_version' => $r->metadata_version, 'published' => (bool) $r->published],
|
||||
'content' => $content, 'source' => $original ? array_intersect_key($original, array_flip(['source_url', 'published_at', 'modified_at', 'legacy_id', 'image_source_url'])) : null, 'images' => $this->publicResources()->whereIn('d.media_type', ['image/png', 'image/jpeg', 'image/webp'])->get(['r.id', 'r.title']),
|
||||
'revisions' => DB::table('portal_resource_revisions')->where('resource_id', $id)->orderByDesc('revision_no')->get(['revision_no', 'change_type', 'created_at'])])->toResponse(request())->header('Cache-Control', 'private, no-store');
|
||||
}
|
||||
|
||||
public function save(Request $request, int $id)
|
||||
{
|
||||
$this->installed();
|
||||
$data = $request->validate(['body' => ['required', 'string', 'max:100000'], 'image_resource_id' => ['nullable', 'integer'], 'metadata_version' => ['required', 'integer', 'min:1']]);
|
||||
$data = $this->validateContent($request);
|
||||
DB::transaction(function () use ($request, $id, $data) {
|
||||
$r = DB::table('portal_resources')->where('id', $id)->lockForUpdate()->first();
|
||||
abort_unless($r, 404);
|
||||
@@ -118,7 +105,7 @@ class EditorialArticleController extends Controller
|
||||
if ($body === '') {
|
||||
throw ValidationException::withMessages(['body' => 'Escreva o corpo do artigo.']);
|
||||
}
|
||||
$content = ['format' => 1, 'body' => $body, 'image_resource_id' => $data['image_resource_id'] ?? null];
|
||||
$content = $this->edition($r, $data);
|
||||
$revision = (int) $r->metadata_version + 1;
|
||||
DB::table('portal_resources')->where('id', $id)->update(['editorial_content' => json_encode($content, JSON_THROW_ON_ERROR), 'published' => false, 'approved_by' => null, 'metadata_version' => $revision, 'last_reviewed_by' => $request->user()->id, 'last_reviewed_at' => now(), 'updated_at' => now()]);
|
||||
$resource = DB::table('portal_resources')->find($id);
|
||||
@@ -130,16 +117,46 @@ class EditorialArticleController extends Controller
|
||||
return redirect('/management/portal-services/resources/'.$id.'/article')->with('success', 'Artigo guardado como rascunho. Publique expressamente no acervo quando estiver pronto.');
|
||||
}
|
||||
|
||||
public function show(int $id)
|
||||
private function validateContent(Request $request): array
|
||||
{
|
||||
return $request->validate(['body' => ['required', 'string', 'max:100000'], 'image_resource_id' => ['nullable', 'integer'], 'image_mode' => ['nullable', 'in:original,selected,none'], 'metadata_version' => ['required', 'integer', 'min:1']]);
|
||||
}
|
||||
|
||||
private function edition(object $resource, array $data): array
|
||||
{
|
||||
$reading = app(\App\Services\EditorialReading::class);
|
||||
$original = $reading->original($resource);
|
||||
$mode = $data['image_mode'] ?? (!empty($data['image_resource_id']) ? 'selected' : 'none');
|
||||
if ($mode === 'selected') abort_unless(!empty($data['image_resource_id']) && $reading->publicResources()->where('r.id', $data['image_resource_id'])->whereIn('d.media_type', ['image/png', 'image/jpeg', 'image/webp'])->exists(), 422, 'Escolha uma imagem pública disponível na biblioteca.');
|
||||
return ['format' => 1, 'body' => trim($data['body']), 'image_mode' => $mode, 'image_resource_id' => $mode === 'selected' ? (int)$data['image_resource_id'] : null,
|
||||
'preserve_original_html' => $original && trim($data['body']) === $reading->plain($original['content_html'])];
|
||||
}
|
||||
|
||||
public function preview(Request $request, int $id)
|
||||
{
|
||||
$this->installed();
|
||||
$r = $this->publicResources()->where('r.id', $id)->first(['r.id', 'r.title', 'r.description', 'r.editorial_content', 'r.document_id', 'd.issuer']);
|
||||
abort_unless($r && $r->editorial_content, 404);
|
||||
$content = json_decode($r->editorial_content, true);
|
||||
// Recheck the original bytes using the same binary gate as the library.
|
||||
$binary = app(VerifiedDocumentBinary::class)->respond($this->publicResources()->where('r.id', $id)->first(['r.*', 'd.*']));
|
||||
$image = ! empty($content['image_resource_id']) ? $this->publicResources()->where('r.id', $content['image_resource_id'])->whereIn('d.media_type', ['image/png', 'image/jpeg', 'image/webp'])->value('r.id') : null;
|
||||
app(\App\Services\DocumentManagementAccess::class)->assertEditableResource($request->user(), $id);
|
||||
$resource = DB::table('portal_resources')->find($id); abort_unless($resource, 404);
|
||||
return Inertia::render('portal/article', ['article' => app(\App\Services\EditorialReading::class)->reading($resource, null, true)])
|
||||
->toResponse($request)->header('Cache-Control', 'private, no-store')->header('X-Robots-Tag', 'noindex, nofollow');
|
||||
}
|
||||
|
||||
return Inertia::render('portal/article', ['article' => ['id' => $id, 'title' => $r->title, 'description' => $r->description, 'author' => $r->issuer, 'body' => $content['body'], 'image_url' => $image ? '/documentos/'.$image.'/view' : null, 'original_url' => '/documentos/'.$id]]);
|
||||
public function previewContent(Request $request, int $id)
|
||||
{
|
||||
$this->installed();
|
||||
app(\App\Services\DocumentManagementAccess::class)->assertEditableResource($request->user(), $id);
|
||||
$resource = DB::table('portal_resources')->find($id); abort_unless($resource, 404);
|
||||
$data = $this->validateContent($request);
|
||||
abort_unless((int)$resource->metadata_version === (int)$data['metadata_version'], 409, 'A revisão mudou; recupere antes de rever a prévia.');
|
||||
return response()->json(['article' => app(\App\Services\EditorialReading::class)->reading($resource, $this->edition($resource, $data), true)])
|
||||
->header('Cache-Control', 'private, no-store')->header('X-Robots-Tag', 'noindex, nofollow');
|
||||
}
|
||||
|
||||
public function show(Request $request, int $id)
|
||||
{
|
||||
$this->installed();
|
||||
$resource = app(\App\Services\EditorialReading::class)->publicResources()->where('r.id', $id)->first(['r.*']);
|
||||
abort_unless($resource, 404);
|
||||
return Inertia::render('portal/article', ['article' => app(\App\Services\EditorialReading::class)->reading($resource)])->toResponse($request)->header('Cache-Control', 'private, no-store');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'));
|
||||
|
||||
@@ -170,8 +170,10 @@ class InvestmentSubmissionController extends Controller
|
||||
// Binding does not save form edits or assert that the context has been reviewed.
|
||||
$this->event($row, $request, 'context_bound');
|
||||
$desk->event($desk->case($request->user(), $case->id), $request->user(), 'manifestation_context_bound', ['submission_id' => $id, 'previous_case_version' => (int) $case->version]);
|
||||
|
||||
return ['case_id' => $case->id, 'version' => $row->version, 'case_version' => (int) $case->version + 1, 'replayed' => false];
|
||||
}, 3);
|
||||
|
||||
return response()->json($result);
|
||||
}
|
||||
|
||||
@@ -190,11 +192,12 @@ class InvestmentSubmissionController extends Controller
|
||||
{
|
||||
$required = $submitting ? 'required' : 'nullable';
|
||||
$data = $request->validate(['version' => ['required', 'integer', 'min:1'],
|
||||
'payload' => ['required', 'array:full_name,email,phone,country,residence_country_code,incorporation_country_code,organization_country_snapshot,declared_origin_country_code,organisation,representative_role,experience,investment_capacity,profile_id,sector,objective,participation_id,partnership_id,estimated_amount,currency,horizon,location,cluster_ids,corridor_ids,project_ids,contact_consent,other_details'],
|
||||
'payload' => ['required', 'array:context_reuse,full_name,email,phone,country,residence_country_code,incorporation_country_code,organization_country_snapshot,declared_origin_country_code,organisation,representative_role,experience,investment_capacity,profile_id,sector,objective,participation_id,partnership_id,estimated_amount,currency,horizon,location,cluster_ids,corridor_ids,project_ids,contact_consent,other_details'],
|
||||
'payload.organization_country_snapshot' => ['sometimes', 'array', 'max:10'],
|
||||
'payload.residence_country_code' => ['nullable', 'string', app(CountryCatalogue::class)->rule()],
|
||||
'payload.incorporation_country_code' => ['nullable', 'string', app(CountryCatalogue::class)->rule()],
|
||||
'payload.declared_origin_country_code' => ['nullable', 'string', app(CountryCatalogue::class)->rule()],
|
||||
'payload.context_reuse' => ['nullable', 'string', 'max:10000'],
|
||||
'payload.other_details' => ['nullable', 'string', 'max:5000'],
|
||||
'payload.full_name' => [$required, 'string', ...($submitting ? ['min:3'] : []), 'max:150'], 'payload.email' => [$required, $submitting ? 'email:rfc' : 'string', 'max:150'],
|
||||
'payload.phone' => ['nullable', 'string', 'max:30'], 'payload.country' => [$submitting ? 'required_without:payload.declared_origin_country_code' : 'nullable', 'nullable', 'string', 'max:100'],
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ use App\Services\CountryCatalogue;
|
||||
use App\Services\IdempotencyConflict;
|
||||
use App\Services\InterestSubmission;
|
||||
use App\Services\LegacyContent;
|
||||
use App\Services\PortalProfileAccess;
|
||||
use App\Services\Malware\MalwareScanner;
|
||||
use App\Services\VerifiedDocumentBinary;
|
||||
use App\Services\VirtualDesk;
|
||||
@@ -274,7 +275,7 @@ class PortalServicesController extends Controller
|
||||
'resource' => $resource,
|
||||
'backUrl' => $backUrl,
|
||||
'downloadUrl' => route('services.download', ['id' => $id]),
|
||||
'articleUrl' => Schema::hasColumn('portal_resources','editorial_content') && DB::table('portal_resources')->where('id',$id)->whereNotNull('editorial_content')->exists() ? '/artigos/'.$id : null,
|
||||
'articleUrl' => $this->articleUrl($id),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -297,6 +298,7 @@ class PortalServicesController extends Controller
|
||||
|
||||
public function manage(Request $request, MalwareScanner $scanner): Response
|
||||
{
|
||||
$managementFilters = $request->validate(['q' => 'nullable|string|max:150', 'state' => 'nullable|in:draft,published,withdrawn', 'intent' => 'nullable|in:document,legislation']);
|
||||
$access = app(\App\Services\DocumentManagementAccess::class);
|
||||
if (! $access->full($request->user())) {
|
||||
$documents = $access->documents($request->user())->where('status', '!=', 'deleted');
|
||||
@@ -304,7 +306,7 @@ class PortalServicesController extends Controller
|
||||
$health['signatures_at'] = $health['signatures_at']?->format(DATE_ATOM);
|
||||
|
||||
$editorial = $access->editorial($request->user());
|
||||
$resources = $editorial ? $access->editorialResources($request->user())->orderByDesc('r.updated_at')
|
||||
$resources = $editorial ? $this->filterManagedResources($access->editorialResources($request->user()), $managementFilters)->orderByDesc('r.updated_at')
|
||||
->get(['r.*', 'd.file_name', 'd.media_type', 'd.size_bytes', 'd.issuer', 'd.status as file_status', 'd.data_classification', 'd.version_no as file_version', 'd.rights_status as file_rights_status', 'd.malware_scan_status', 'd.malware_failure_reason']) : collect();
|
||||
$links = DB::table('portal_resource_links')->whereIn('resource_id', $resources->pluck('id'))->orderBy('id')->get()->groupBy('resource_id');
|
||||
$revisions = DB::table('portal_resource_revisions')->whereIn('resource_id', $resources->pluck('id'))->orderByDesc('revision_no')->get(['resource_id', 'revision_no', 'change_type', 'actor_user_id', 'created_at'])->groupBy('resource_id');
|
||||
@@ -313,6 +315,7 @@ class PortalServicesController extends Controller
|
||||
$resource->revisions = $revisions->get($resource->id, collect())->values();
|
||||
});
|
||||
return Inertia::render('portal/manage', [
|
||||
'managementFilters' => $managementFilters, 'associationOptions' => $this->contentAssociations($request),
|
||||
'documentAccessOnly' => ! $editorial, 'editorialScopeOnly' => $editorial, 'resources' => $resources, 'partnerships' => [], 'territories' => [],
|
||||
'requirementCatalogue' => [], 'requirementAssociationsEnabled' => false,
|
||||
'disclosureAuthorization' => null, 'incompleteCount' => $resources->filter(fn ($r) => ! $r->description || ! $r->issuer || ! $r->source_url || $r->source_confirmation === 'unconfirmed' || $r->legal_status === 'unknown' || $r->rights_status === 'unconfirmed')->count(), 'possibleDuplicates' => $resources->whereNotNull('duplicate_of_resource_id')->count(),
|
||||
@@ -331,7 +334,7 @@ class PortalServicesController extends Controller
|
||||
->where('external_id', 'document-repository-disclosure-authorization-2026-10-06-v1')
|
||||
->where('action', 'public_disclosure_authorized')->first(['external_id', 'occurred_at', 'after_data']);
|
||||
$disclosureAuthorizationData = $disclosureAuthorization ? json_decode($disclosureAuthorization->after_data, true) : null;
|
||||
$resources = DB::table('portal_resources as r')->join('documents as d', 'd.id', '=', 'r.document_id')->orderByDesc('r.updated_at')->limit(200)
|
||||
$resources = $this->filterManagedResources(DB::table('portal_resources as r')->join('documents as d', 'd.id', '=', 'r.document_id')->whereNull('d.deleted_at'), $managementFilters)->orderByDesc('r.updated_at')->limit(200)
|
||||
->get(['r.*', 'd.file_name', 'd.media_type', 'd.size_bytes', 'd.issuer', 'd.status as file_status', 'd.data_classification', 'd.version_no as file_version', 'd.rights_status as file_rights_status', 'd.malware_scan_status', 'd.malware_failure_reason']);
|
||||
$links = DB::table('portal_resource_links')->whereIn('resource_id', $resources->pluck('id'))->orderBy('id')->get()->groupBy('resource_id');
|
||||
$resources->each(fn ($resource) => $resource->links = $links->get($resource->id, collect())->values());
|
||||
@@ -342,6 +345,7 @@ class PortalServicesController extends Controller
|
||||
$malwareHealth['signatures_at'] = $malwareHealth['signatures_at']?->format(DATE_ATOM);
|
||||
|
||||
return Inertia::render('portal/manage', ['resources' => $resources,
|
||||
'managementFilters' => $managementFilters, 'associationOptions' => $this->contentAssociations($request),
|
||||
'requirementCatalogue' => $this->requirementEvidenceEnabled()
|
||||
? collect(is_array($catalogue = config('document-requirements.catalogue', [])) ? $catalogue : [])->map(fn ($item, $key) => ['key' => $key, ...$item])->values()
|
||||
: collect(),
|
||||
@@ -367,6 +371,39 @@ class PortalServicesController extends Controller
|
||||
'possibleDuplicates' => DB::table('portal_resources')->whereNotNull('duplicate_of_resource_id')->count()]);
|
||||
}
|
||||
|
||||
private function filterManagedResources(Builder $query, array $filters): Builder
|
||||
{
|
||||
if (! empty($filters['q'])) {
|
||||
$term = '%'.addcslashes($filters['q'], '%_\\').'%';
|
||||
$query->where(fn ($q) => $q->where('r.title', 'ilike', $term)->orWhere('r.document_number', 'ilike', $term)->orWhere('d.issuer', 'ilike', $term)->orWhere('d.file_name', 'ilike', $term));
|
||||
}
|
||||
if (! empty($filters['state'])) {
|
||||
$query->where('r.published', $filters['state'] === 'published');
|
||||
if ($filters['state'] !== 'published') {
|
||||
$method = $filters['state'] === 'withdrawn' ? 'whereExists' : 'whereNotExists';
|
||||
$query->$method(fn ($q) => $q->selectRaw('1')->from('portal_resource_revisions as history')->whereColumn('history.resource_id', 'r.id')->whereIn('history.change_type', ['unpublished', 'withdrawn']));
|
||||
}
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
|
||||
private function contentAssociations(Request $request): array
|
||||
{
|
||||
$access = app(\App\Services\DocumentManagementAccess::class);
|
||||
$resources = $access->full($request->user()) ? DB::table('portal_resources as r')->join('documents as d', 'd.id', '=', 'r.document_id')->whereNull('d.deleted_at')
|
||||
: ($access->editorial($request->user()) ? $access->editorialResources($request->user()) : null);
|
||||
$documents = $resources ? $resources->orderBy('r.title')->get(['r.id as value', 'r.title as label', 'r.document_type']) : collect();
|
||||
return [
|
||||
'sector' => \App\Models\Sector::choices()->orderBy('name')->get(['id as value', 'name as label']),
|
||||
'territory' => app(\App\Services\InvestmentGeography::class)->publicReferences()->orderBy('name')->get(['id as value', 'name as label']),
|
||||
'opportunity' => app(PortalProfileAccess::class)->scope(\App\Models\Opportunity::query(), $request->user())->orderBy('title')->get(['id as value', 'title as label']),
|
||||
'document' => $documents,
|
||||
'procedure' => $documents->where('document_type', 'procedure')->values(),
|
||||
'stage' => collect(config('virtual-desk.steps', []))->map(fn ($step, $key) => ['value' => $key, 'label' => $step['title']])->values(),
|
||||
'activity' => app(\App\Services\EconomicReferenceCatalog::class)->activities()->map(fn ($row) => ['value' => $row->id, 'label' => $row->display_label])->values(),
|
||||
];
|
||||
}
|
||||
|
||||
public function upload(Request $request): RedirectResponse
|
||||
{
|
||||
$this->validateManagement($request, ['file' => ['required', 'file', 'mimetypes:application/pdf,text/plain,text/csv,image/jpeg,image/png,image/webp,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.oasis.opendocument.text'.(app(LegacyContent::class)->available() ? ',application/json' : ''), 'max:10240'],
|
||||
@@ -399,7 +436,7 @@ class PortalServicesController extends Controller
|
||||
|
||||
ScanDocumentForMalware::dispatch($id)->afterCommit();
|
||||
|
||||
return to_route('services.manage')->with('success', 'Documento recebido em armazenamento privado e quarentena. Não aparece na biblioteca pública até validação e publicação explícitas.');
|
||||
return to_route('services.manage', array_filter(['intent' => in_array($request->input('intent'), ['document', 'legislation'], true) ? $request->input('intent') : null]))->with('uploaded_document_id', $id)->with('success', 'Documento recebido em armazenamento privado e quarentena. Não aparece na biblioteca pública até validação e publicação explícitas.');
|
||||
}
|
||||
|
||||
public function resource(Request $request): RedirectResponse
|
||||
@@ -663,7 +700,7 @@ class PortalServicesController extends Controller
|
||||
'data_classification' => ['nullable', 'in:internal,public'],
|
||||
'source_reference' => ['required', 'string', 'min:10', 'max:2000'],
|
||||
'legislation' => ['required', 'boolean'], 'essential' => ['required', 'boolean'],
|
||||
'associations' => ['nullable', 'array', 'max:40'], 'associations.*.target_type' => ['required', 'in:theme,module,activity,stage,procedure,entity,territory,document,investment_type,legal_source'.($this->requirementEvidenceEnabled() ? ',requirement' : '')],
|
||||
'associations' => ['nullable', 'array', 'max:40'], 'associations.*.target_type' => ['required', 'in:theme,module,activity,stage,procedure,entity,territory,document,investment_type,legal_source,sector,opportunity'.($this->requirementEvidenceEnabled() ? ',requirement' : '')],
|
||||
'associations.*.target_key' => ['required', 'string', 'max:180'],
|
||||
'associations.*.review_status' => ['nullable', 'in:pending,confirmed'],
|
||||
'associations.*.relationship' => ['required', 'in:applies_to,supports,version_of,translation_of,amends,repeals,implements,cites,related_to'],
|
||||
@@ -829,4 +866,17 @@ SQL, [$id]);
|
||||
|
||||
return $ids ?: [$id];
|
||||
}
|
||||
private function articleUrl(int $id): ?string
|
||||
{
|
||||
if (!Schema::hasColumn('portal_resources', 'editorial_content')) return null;
|
||||
$resource = DB::table('portal_resources')->find($id);
|
||||
if (!$resource) return null;
|
||||
if ($resource->editorial_content !== null) return '/artigos/'.$id;
|
||||
try {
|
||||
return app(\App\Services\EditorialReading::class)->original($resource) ? '/artigos/'.$resource->id : null;
|
||||
} catch (\Symfony\Component\HttpKernel\Exception\HttpException) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Services\Procurement;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class ProcurementController extends Controller
|
||||
{
|
||||
private const STATE_SQL = "CASE WHEN v.payload->>'procedural_state' IN ('cancelled','results') THEN v.payload->>'procedural_state' WHEN ((v.payload->>'deadline')::timestamp AT TIME ZONE (v.payload->>'timezone'))<=CURRENT_TIMESTAMP THEN 'deadline_ended' WHEN v.payload->>'extended'='true' THEN 'extended' ELSE 'open' END";
|
||||
|
||||
private function query(Procurement $service)
|
||||
{
|
||||
abort_unless($service->available(), 404);
|
||||
|
||||
return DB::table('portal_tenders as t')->join('portal_tender_versions as v', fn ($q) => $q->on('v.id', '=', 't.published_version_id')->on('v.tender_id', '=', 't.id'))->whereNotNull('v.published_at');
|
||||
}
|
||||
|
||||
public function index(Request $request, Procurement $service)
|
||||
{
|
||||
$filters = $request->validate(['q' => 'nullable|string|max:150', 'entity' => 'nullable|uuid', 'sector' => 'nullable|integer', 'territory' => 'nullable|uuid', 'modality' => 'nullable|string|max:200', 'financier' => 'nullable|string|max:200', 'state' => 'nullable|in:open,extended,deadline_ended,cancelled,results', 'deadline_before' => 'nullable|date_format:Y-m-d', 'regime' => 'nullable|in:state,external,private,other']);
|
||||
$filters = array_filter($filters, fn ($value) => $value !== null && $value !== '');
|
||||
$query = $this->query($service);
|
||||
if (! empty($filters['q'])) {
|
||||
$query->where(fn ($q) => $q->whereRaw("v.payload->>'title' ILIKE ?", ['%'.$filters['q'].'%'])->orWhereRaw("v.payload->>'object' ILIKE ?", ['%'.$filters['q'].'%'])->orWhereRaw("v.payload->>'reference' ILIKE ?", ['%'.$filters['q'].'%']));
|
||||
}
|
||||
foreach (['entity' => 'organization_id', 'sector' => 'sector_id', 'modality' => 'modality', 'financier' => 'financier', 'regime' => 'regime'] as $filter => $field) {
|
||||
if (! empty($filters[$filter])) {
|
||||
$query->whereRaw('v.payload->>? = ?', [$field, (string) $filters[$filter]]);
|
||||
}
|
||||
}
|
||||
if (! empty($filters['territory'])) {
|
||||
$query->whereRaw("v.payload->>'territory_id' IN (WITH RECURSIVE scope(id,depth) AS (SELECT id,0 FROM geo_locations WHERE id=? AND deleted_at IS NULL UNION ALL SELECT c.id,s.depth+1 FROM geo_locations c JOIN scope s ON c.parent_id=s.id WHERE c.deleted_at IS NULL AND s.depth<4) SELECT id::text FROM scope)", [$filters['territory']]);
|
||||
}
|
||||
if (! empty($filters['state'])) {
|
||||
$query->whereRaw(self::STATE_SQL.' = ?', [$filters['state']]);
|
||||
}
|
||||
if (! empty($filters['deadline_before'])) {
|
||||
$query->whereRaw("left(v.payload->>'deadline',10)<=?", [$filters['deadline_before']]);
|
||||
}
|
||||
$rows = $query->select('t.id', 'v.id as vid')->orderByDesc('v.published_at')->paginate(20)->withQueryString();
|
||||
$rows->through(fn ($r) => ['id' => $r->id, ...$this->summary($service, $r->vid)]);
|
||||
$facets = $this->query($service)->get(['v.payload'])->map(fn ($r) => json_decode($r->payload, true));
|
||||
$options = [];
|
||||
foreach (['entity' => ['organization_id', 'organization_name'], 'sector' => ['sector_id', 'sector_name'], 'territory' => ['territory_id', 'territory_name'], 'modality' => ['modality', 'modality'], 'financier' => ['financier', 'financier']] as $name => [$value,$label]) {
|
||||
$options[$name] = $facets->filter(fn ($p) => ! empty($p[$value]))->map(fn ($p) => ['id' => (string) $p[$value], 'name' => $p[$label]])->unique('id')->values();
|
||||
}
|
||||
|
||||
return Inertia::render('procurement/index', ['items' => $rows, 'filters' => $filters, 'options' => $options, 'regimes' => config('procurement.regimes'), 'management' => $service->access($request->user())])->toResponse($request)->header('Cache-Control', 'private, no-store');
|
||||
}
|
||||
|
||||
private function summary(Procurement $service, string $vid): array
|
||||
{
|
||||
$v = $service->publishedVersion($vid);
|
||||
|
||||
return ['publication' => $v, 'state' => $service->state($v)];
|
||||
}
|
||||
|
||||
public function show(Request $request, string $id, Procurement $service)
|
||||
{
|
||||
return $this->detail($request, $id, $service, false);
|
||||
}
|
||||
|
||||
public function print(Request $request, string $id, Procurement $service)
|
||||
{
|
||||
return $this->detail($request, $id, $service, true);
|
||||
}
|
||||
|
||||
private function detail(Request $request, string $id, Procurement $service, bool $print)
|
||||
{
|
||||
$row = $this->query($service)->where('t.id', $id)->firstOrFail(['t.id', 'v.id as vid']);
|
||||
$filters = $request->only(['q', 'entity', 'sector', 'territory', 'modality', 'financier', 'state', 'deadline_before', 'regime', 'page']);
|
||||
|
||||
return Inertia::render('procurement/show', ['id' => $id, ...$this->summary($service, $row->vid), 'printMode' => $print, 'backUrl' => '/concursos'.($filters ? '?'.http_build_query($filters) : ''),
|
||||
'history' => DB::table('portal_tender_versions')->where('tender_id', $id)->whereNotNull('published_at')->orderBy('version')->pluck('id')->map(fn ($vid) => $service->publishedVersion($vid)),
|
||||
'watching' => $request->user() && DB::table('portal_tender_watches')->where('tender_id', $id)->where('user_id', $request->user()->id)->exists()])->toResponse($request)->header('Cache-Control', 'private, no-store');
|
||||
}
|
||||
|
||||
public function manage(Request $request, Procurement $service)
|
||||
{
|
||||
$service->guard($request->user());
|
||||
|
||||
return Inertia::render('procurement/manage', ['items' => DB::table('portal_tenders')->orderByDesc('updated_at')->get(['id', 'draft', 'version', 'published_version_id'])->map(fn ($r) => ['id' => $r->id, 'title' => json_decode($r->draft, true)['title'] ?? 'Rascunho sem título', 'version' => $r->version, 'published' => (bool) $r->published_version_id])])->toResponse($request)->header('Cache-Control', 'private, no-store');
|
||||
}
|
||||
|
||||
public function edit(Request $request, Procurement $service, ?string $id = null)
|
||||
{
|
||||
$service->guard($request->user());
|
||||
$r = $id ? $service->record($id) : null;
|
||||
|
||||
return Inertia::render('procurement/edit', ['record' => $r ? ['id' => $id, 'version' => $r->version, 'draft' => json_decode($r->draft, true), 'published' => (bool) $r->published_version_id] : null,
|
||||
'references' => $service->references($request->user()), 'history' => $id ? DB::table('portal_tender_versions')->where('tender_id', $id)->orderByDesc('version')->get(['version', 'act', 'reason', 'actor_id', 'published_at', 'created_at']) : []])->toResponse($request)->header('Cache-Control', 'private, no-store');
|
||||
}
|
||||
|
||||
public function save(Request $request, Procurement $service, ?string $id = null)
|
||||
{
|
||||
$id = $service->save($request->user(), $id, $request->all());
|
||||
|
||||
return redirect('/management/concursos/'.$id)->with('success', 'Rascunho guardado. A publicação anterior permanece intacta.');
|
||||
}
|
||||
|
||||
public function preview(Request $request, string $id, Procurement $service)
|
||||
{
|
||||
return response()->json($service->preview($request->user(), $id, (string) $request->input('act')))->header('Cache-Control', 'private, no-store');
|
||||
}
|
||||
|
||||
public function publish(Request $request, string $id, Procurement $service)
|
||||
{
|
||||
$service->publish($request->user(), $id, $request->all());
|
||||
|
||||
return redirect('/management/concursos/'.$id)->with('success', 'Acto publicado, com versão e auditoria. Nenhuma proposta recebida pelo portal.');
|
||||
}
|
||||
|
||||
public function watch(Request $request, string $id, Procurement $service)
|
||||
{
|
||||
$row = $this->query($service)->where('t.id', $id)->firstOrFail(['v.id']);
|
||||
DB::table('portal_tender_watches')->updateOrInsert(['tender_id' => $id, 'user_id' => $request->user()->id], ['seen_version_id' => $row->id, 'created_at' => now(), 'updated_at' => now()]);
|
||||
|
||||
return back()->with('success', 'Concurso guardado para acompanhamento. Isto não apresenta uma proposta.');
|
||||
}
|
||||
|
||||
public function unwatch(Request $request, string $id, Procurement $service)
|
||||
{
|
||||
abort_unless($service->available(), 404);
|
||||
DB::table('portal_tender_watches')->where('tender_id', $id)->where('user_id', $request->user()->id)->delete();
|
||||
|
||||
return back();
|
||||
}
|
||||
|
||||
public function watched(Request $request, Procurement $service)
|
||||
{
|
||||
$rows = $this->query($service)->join('portal_tender_watches as w', 'w.tender_id', '=', 't.id')->where('w.user_id', $request->user()->id)->get(['t.id', 'v.id as vid', 'w.seen_version_id'])->map(fn ($r) => ['id' => $r->id, 'changed' => $r->seen_version_id !== $r->vid, ...$this->summary($service, $r->vid)]);
|
||||
|
||||
return Inertia::render('procurement/manage', ['watched' => true, 'items' => $rows])->toResponse($request)->header('Cache-Control', 'private, no-store');
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ class EnsureScopedEditorial
|
||||
return $next($request);
|
||||
}
|
||||
abort_unless($access->editorial($actor), 403);
|
||||
$allowed = in_array($request->route()->getName(), ['editorial.edit', 'editorial.save', 'services.resource', 'services.resource.update'], true)
|
||||
$allowed = in_array($request->route()->getName(), ['editorial.edit', 'editorial.save', 'editorial.preview', 'editorial.preview-content', 'services.resource', 'services.resource.update'], true)
|
||||
|| ($request->route()->getName() === 'services.moderate' && $request->route('type') === 'resources');
|
||||
abort_unless($allowed, 403);
|
||||
if ($request->route('id') !== null) {
|
||||
|
||||
@@ -19,7 +19,8 @@ final class EnsureScopedPortalAdministration
|
||||
$path = $request->path();
|
||||
$interests = $request->is('management/interests', 'management/interests/*', 'api/v1/investment/admin/interests/*');
|
||||
// Fiscal maintenance reuses authorised administration; unrelated gates remain unchanged.
|
||||
$catalogue = $request->is('management/economic-references/lot-01', 'management/economic-references/lot-01/*',
|
||||
$catalogue = $request->is('management/territory-geometry-import', 'management/territory-geometry-import/*',
|
||||
'management/economic-references/lot-01', 'management/economic-references/lot-01/*',
|
||||
'management/education', 'management/education/*', 'management/investment/references', 'management/investment/references/*', 'api/v1/investment/admin/references', 'api/v1/investment/admin/references/*',
|
||||
'management/investment/zones', 'management/investment/zones/*', 'api/v2/investment/admin/zones', 'api/v2/investment/admin/zones/*',
|
||||
'management/balcao/atribuicoes', 'management/balcao/atribuicoes/*', 'management/balcao/servicos',
|
||||
|
||||
@@ -51,8 +51,10 @@ class HandleInertiaRequests extends Middleware
|
||||
return [
|
||||
...parent::share($request),
|
||||
'name' => config('app.name'),
|
||||
'procurementEnabled' => app(\App\Services\Procurement::class)->available(),
|
||||
'homologationReserved' => (bool) config('portal.homologation_only', false),
|
||||
'profileAccess' => app(PortalProfileAccess::class)->navigation($request->user()),
|
||||
'contentManagementHref' => app(\App\Services\ContentManagement::class)->access($request->user()) !== [] ? '/gestao-conteudos' : null,
|
||||
'portalOperator' => config('portal.operator_name'),
|
||||
'portalInstitutionalContactEmail' => filter_var(config('portal.institutional_contact_email'), FILTER_VALIDATE_EMAIL) ?: null,
|
||||
'legacyContentEnabled' => app(LegacyContent::class)->available(),
|
||||
@@ -68,7 +70,7 @@ class HandleInertiaRequests extends Middleware
|
||||
'create' => ['label' => 'Nova oportunidade', 'href' => '/management/opportunities/create'],
|
||||
'installationHref' => '/management/opportunity-management-install',
|
||||
] : null,
|
||||
'flash' => ['success' => fn () => $request->session()->get('success'), 'warning' => fn () => $request->session()->get('warning')],
|
||||
'flash' => ['success' => fn () => $request->session()->get('success'), 'warning' => fn () => $request->session()->get('warning'), 'uploaded_document_id' => fn () => $request->session()->get('uploaded_document_id')],
|
||||
'scopedAdministration' => $request->user() && app(\App\Services\ScopedPortalAdministration::class)->administrative($request->user()) ? [
|
||||
['label'=>'Zonas e parques','href'=>'/management/investment/zones'],
|
||||
['label'=>'Referências económicas','href'=>'/management/economic-references/lot-01'],
|
||||
|
||||
@@ -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.');
|
||||
}
|
||||
|
||||
@@ -33,7 +33,8 @@ class StoreLandAssistanceCaseRequest extends FormRequest
|
||||
return [
|
||||
'version' => [$this->isMethod('post') && ! $this->routeIs('land-assistance.support') ? 'nullable' : 'required', 'integer', 'min:1'],
|
||||
'context_revision' => ['nullable', 'integer', 'min:0'], 'context_reviewed' => ['nullable', 'boolean'],
|
||||
'payload' => ['required', 'array:support_kind,request_description,project_reference,investor_path_case_id,applicant_category,full_name,contact_person,email,phone,preferred_contact,country,declared_origin_country_code,nationality,representative_name,representative_role,representation_basis,nuit,constitution_or_residency_note,area_status,parcels,purpose,activity,known_occupants,occupant_description,known_improvements,improvements_description,prior_right_status,prior_right_description,conflict_status,conflict_description,official_process,attachments_to_follow,exploration_plan,consultation,additional_information,truth_declaration,contact_consent'],
|
||||
'payload' => ['required', 'array:context_reuse,support_kind,request_description,project_reference,investor_path_case_id,applicant_category,full_name,contact_person,email,phone,preferred_contact,country,declared_origin_country_code,nationality,representative_name,representative_role,representation_basis,nuit,constitution_or_residency_note,area_status,parcels,purpose,activity,known_occupants,occupant_description,known_improvements,improvements_description,prior_right_status,prior_right_description,conflict_status,conflict_description,official_process,attachments_to_follow,exploration_plan,consultation,additional_information,truth_declaration,contact_consent'],
|
||||
'payload.context_reuse' => ['nullable', 'string', 'max:10000'],
|
||||
'payload.support_kind' => ['nullable', 'in:investment,home,family_exploitation,recognition,regularization,delimitation,change_of_use,transfer,existing_process,conflict_guidance,other'],
|
||||
'payload.request_description' => ['nullable', 'string', 'max:4000'],
|
||||
'payload.project_reference' => ['nullable', 'string', 'max:160'],
|
||||
|
||||
@@ -37,7 +37,7 @@ class StoreSimulationFormDraftRequest extends FormRequest
|
||||
'contact_province', 'contact_district', 'preferred_channel',
|
||||
];
|
||||
$economic = [
|
||||
'journey_type', 'detail_level', 'business_plan_snapshot', 'financial_tools_snapshot', 'cashflow_snapshot', 'project_team_snapshot', 'project_execution_snapshot', 'purpose', 'manifestation_type', 'preferred_language', 'project_title', 'project_summary', 'project_problem', 'target_customers', 'value_proposition', 'market_assumption', 'market_source', 'project_milestones', 'selection_labels', 'project_type',
|
||||
'context_applicability_snapshot', 'investment_regime', 'context_reuse', 'compatible_fields_snapshot', 'published_iva_base', 'published_iva_currency', 'published_iva_date', 'published_iva_scope', 'monthly_iva_settings', 'monthly_iva_snapshot', 'journey_type', 'detail_level', 'business_plan_snapshot', 'financial_tools_snapshot', 'cashflow_snapshot', 'project_team_snapshot', 'project_execution_snapshot', 'purpose', 'manifestation_type', 'preferred_language', 'project_title', 'project_summary', 'project_problem', 'target_customers', 'value_proposition', 'market_assumption', 'market_source', 'project_milestones', 'selection_labels', 'project_type',
|
||||
'competitor_assumption', 'sales_channel_assumption', 'marketing_approach', 'location_rationale', 'project_risk_responses_snapshot', 'project_questionnaire_version',
|
||||
'customer_validation_status', 'cost_estimate_status', 'location_assessment_status', 'funding_status', 'receivables_timing_status',
|
||||
'project_type_other', 'sector_id', 'activity_id', 'products_services', 'maturity_stage', 'expected_start_date',
|
||||
@@ -72,6 +72,16 @@ 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: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'],
|
||||
'economic_snapshot.published_iva_base' => ['nullable', 'string', 'max:40'],
|
||||
'economic_snapshot.published_iva_currency' => ['nullable', 'string', 'max:3'],
|
||||
'economic_snapshot.published_iva_date' => ['nullable', 'string', 'max:10'],
|
||||
'economic_snapshot.published_iva_scope' => ['nullable', 'string', 'in:yes,no'],
|
||||
'economic_snapshot.monthly_iva_settings' => ['nullable', 'string', 'max:2000'],
|
||||
'economic_snapshot.monthly_iva_snapshot' => ['nullable', 'string', 'max:60000'],
|
||||
'economic_snapshot.project_team_snapshot' => ['nullable', 'string', 'max:30000'],
|
||||
'economic_snapshot.project_execution_snapshot' => ['nullable', 'string', 'max:30000'],
|
||||
'economic_snapshot.project_risk_responses_snapshot' => ['nullable', 'string', 'max:30000'],
|
||||
|
||||
@@ -22,6 +22,12 @@ class StoreSimulationRequest extends FormRequest
|
||||
'date_format' => 'Indique uma data de exigibilidade válida.',
|
||||
'boolean' => 'Indique se declara o enquadramento da operação.',
|
||||
'prohibited' => 'O campo :attribute não pode ser definido pelo cliente neste cálculo.',
|
||||
'array' => 'Indique uma lista válida em :attribute.',
|
||||
'monthly_sales.min' => 'Indique pelo menos um mês de vendas.',
|
||||
'monthly_sales.max' => 'Este lote aceita até 12 meses dentro de 2026.',
|
||||
'monthly_sales.*.date.regex' => 'Indique o primeiro dia de um mês de 2026.',
|
||||
'monthly_sales.*.date.distinct' => 'Cada mês deve aparecer apenas uma vez.',
|
||||
'monthly_sales.*.amount.present' => 'Indique o valor das vendas ou deixe-o explicitamente sem informação.',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -29,13 +35,31 @@ class StoreSimulationRequest extends FormRequest
|
||||
{
|
||||
return ['base' => 'base tributável', 'currency' => 'moeda', 'reference_date' => 'data de exigibilidade',
|
||||
'operation' => 'operação', 'scope_declared' => 'declaração de enquadramento', 'scenario_kind' => 'tipo de cálculo',
|
||||
'rule_id' => 'regra', 'rule_version' => 'versão da regra', 'rate' => 'taxa', 'reduction' => 'redução'];
|
||||
'rule_id' => 'regra', 'rule_version' => 'versão da regra', 'rate' => 'taxa', 'reduction' => 'redução',
|
||||
'monthly_sales' => 'vendas mensais', 'monthly_sales.*.date' => 'mês da projecção',
|
||||
'monthly_sales.*.amount' => 'vendas declaradas do mês', 'sales_vat_basis' => 'tratamento do IVA nos preços'];
|
||||
}
|
||||
|
||||
/** @return array<string, list<mixed>> */
|
||||
public function rules(): array
|
||||
{
|
||||
if ($this->input('scenario_kind') === 'fiscal_rule_published') {
|
||||
if ($this->exists('monthly_sales')) {
|
||||
return [
|
||||
'scenario_kind' => ['required', 'in:fiscal_rule_published'],
|
||||
'monthly_sales' => ['required', 'array', 'min:1', 'max:12'],
|
||||
'monthly_sales.*' => ['required', 'array:date,amount'],
|
||||
'monthly_sales.*.date' => ['required', 'date_format:Y-m-d', 'regex:/^2026-\d{2}-01$/D', 'distinct'],
|
||||
'monthly_sales.*.amount' => ['present', 'nullable', 'string', 'max:40', 'regex:/^\d+(?:\.\d+)?$/D'],
|
||||
'sales_vat_basis' => ['required', 'in:net,included,unknown'],
|
||||
'currency' => ['required', 'in:MZN'],
|
||||
'scope_declared' => ['required', 'boolean'],
|
||||
'organization_id' => ['nullable', 'uuid', 'exists:organizations,id'],
|
||||
'base' => ['prohibited'], 'rate' => ['prohibited'], 'reduction' => ['prohibited'],
|
||||
'rule_id' => ['prohibited'], 'rule_version' => ['prohibited'], 'fiscal_preview' => ['prohibited'],
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'scenario_kind' => ['required', 'in:fiscal_rule_published'],
|
||||
'base' => ['required', 'string', 'max:40', 'regex:/^\d+(?:\.\d+)?$/'],
|
||||
|
||||
@@ -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',
|
||||
}];
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Opportunity;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/** Navigation over existing authorisations; never grants a role or publishes content. */
|
||||
final class ContentManagement
|
||||
{
|
||||
public function access(?User $actor): array
|
||||
{
|
||||
if (! $actor || ! $actor->hasVerifiedEmail()) return [];
|
||||
$profiles = app(PortalProfileAccess::class);
|
||||
$navigation = $profiles->navigation($actor);
|
||||
$legacy = $profiles->legacyWriteAllowed($actor);
|
||||
$documents = app(DocumentManagementAccess::class);
|
||||
$editor = $documents->full($actor) || $documents->editorial($actor);
|
||||
$upload = $editor || $documents->limited($actor);
|
||||
$result = [];
|
||||
if (app(Procurement::class)->available() && app(Procurement::class)->access($actor)) $result['procurement'] = ['index' => '/management/concursos', 'create' => '/management/concursos/novo'];
|
||||
if ($legacy || ($navigation['mine'] ?? null) || ($navigation['review'] ?? null)) {
|
||||
$result['opportunities'] = ['index' => $legacy ? '/management/opportunities' : '/minhas-oportunidades',
|
||||
'create' => $legacy ? '/management/opportunities/create' : ($navigation['create'] ?? null), 'profileWorkflow' => ! $legacy];
|
||||
}
|
||||
if ($upload) $result['documents'] = ['index' => '/management/portal-services', 'edit' => $editor];
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function view(User $actor): array
|
||||
{
|
||||
$access = $this->access($actor);
|
||||
abort_unless($access !== [], 403);
|
||||
$counts = [];
|
||||
if (isset($access['opportunities'])) {
|
||||
$counts['opportunities'] = app(PortalProfileAccess::class)->scope(Opportunity::query(), $actor)
|
||||
->selectRaw("COALESCE(workflow_context->>'working_state',status) as state, count(*) as total")
|
||||
->groupByRaw("COALESCE(workflow_context->>'working_state',status)")->pluck('total', 'state');
|
||||
}
|
||||
if (isset($access['documents'])) {
|
||||
$documents = app(DocumentManagementAccess::class);
|
||||
$query = $documents->full($actor) ? DB::table('portal_resources as r')->join('documents as d', 'd.id', '=', 'r.document_id')->whereNull('d.deleted_at')
|
||||
: ($access['documents']['edit'] ? $documents->editorialResources($actor) : null);
|
||||
$counts['documents'] = $query ? $query->select('r.published', DB::raw('count(*) as total'))->groupBy('r.published')->get()
|
||||
->mapWithKeys(fn ($row) => [$row->published ? 'published' : 'unpublished' => (int) $row->total]) : [];
|
||||
}
|
||||
return ['access' => $access, 'counts' => $counts];
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -26,8 +26,9 @@ class DocumentManagementAccess
|
||||
/** Restore the approved editorial operation without a global management grant. */
|
||||
public function editorial(User $actor): bool
|
||||
{
|
||||
return config('portal.homologation_only', false)
|
||||
&& in_array(strtolower(trim($actor->email)), config('homologation_editorial.accounts', []), true)
|
||||
// Tempo's named editorial designation does not depend on whether the
|
||||
// site's technical reviewer reservation is enabled.
|
||||
return in_array(strtolower(trim($actor->email)), config('homologation_editorial.accounts', []), true)
|
||||
&& app(PortalProfileAccess::class)->superuser($actor)
|
||||
&& ! app(PortalProfileAccess::class)->has($actor, 'auditor')
|
||||
&& app(PortalProfileAccess::class)->mfa($actor);
|
||||
@@ -49,6 +50,11 @@ class DocumentManagementAccess
|
||||
->where('d.external_id', $item['external_id']);
|
||||
});
|
||||
}
|
||||
foreach (json_decode(file_get_contents(config_path('legacy_editorial_resources.json')), true, 512, JSON_THROW_ON_ERROR) as $item) {
|
||||
// These exact original bytes contain the legacy identity/source.
|
||||
// Destination upload IDs differ; the approved file scope does not.
|
||||
$query->orWhere('d.sha256', $item['sha256']);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use GuzzleHttp\Psr7\Uri;
|
||||
use GuzzleHttp\Psr7\UriResolver;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
|
||||
/** Reads verified originals without writing a derived edition or changing publication. */
|
||||
final class EditorialReading
|
||||
{
|
||||
public function publicResources()
|
||||
{
|
||||
return DB::table('portal_resources as r')->join('documents as d', 'd.id', '=', 'r.document_id')
|
||||
->where('r.published', true)->where('r.rights_status', 'authorized')->where('d.rights_status', 'authorized')
|
||||
->where('d.status', 'active')->where('d.data_classification', 'public')->whereNull('d.deleted_at')
|
||||
->where('d.malware_scan_status', 'clean')->whereColumn('d.sha256', 'd.malware_scanned_sha256');
|
||||
}
|
||||
|
||||
public function original(object $resource): ?array
|
||||
{
|
||||
$document = DB::table('documents')->find($resource->document_id);
|
||||
if (! $document || $document->status !== 'active' || $document->malware_scan_status !== 'clean' || ! in_array($document->media_type, ['text/plain', 'application/json'], true)) {
|
||||
return null;
|
||||
}
|
||||
$binary = app(VerifiedDocumentBinary::class)->respond($document);
|
||||
$bytes = $binary instanceof BinaryFileResponse ? file_get_contents($binary->getFile()->getPathname()) : $binary->getContent();
|
||||
if (! is_string($bytes) || strlen($bytes) > 10485760) {
|
||||
return null;
|
||||
}
|
||||
$original = json_decode($bytes, true);
|
||||
|
||||
return is_array($original) && ($original['format'] ?? '') === 'investmoz-legacy-article-v1' && is_string($original['content_html'] ?? null) ? $original : null;
|
||||
}
|
||||
|
||||
public function plain(string $html): string
|
||||
{
|
||||
return trim(html_entity_decode(strip_tags(preg_replace('~</(?:p|div|li|h[1-6]|tr)>|<br\s*/?>~i', "\n\n", $html)), ENT_QUOTES | ENT_HTML5, 'UTF-8'));
|
||||
}
|
||||
|
||||
public function content(object $resource, ?array $original): array
|
||||
{
|
||||
return $resource->editorial_content ? json_decode($resource->editorial_content, true, 512, JSON_THROW_ON_ERROR)
|
||||
: ['body' => $this->plain($original['content_html'] ?? ''), 'image_resource_id' => null, 'image_mode' => 'original', 'preserve_original_html' => true];
|
||||
}
|
||||
|
||||
private function sourceUrl(mixed $url, ?string $base = null): ?string
|
||||
{
|
||||
if (! is_string($url) || trim($url) === '') {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
$absolute = $base ? (string) UriResolver::resolve(new Uri($base), new Uri($url)) : $url;
|
||||
|
||||
return filter_var($absolute, FILTER_VALIDATE_URL) && in_array(strtolower((string) parse_url($absolute, PHP_URL_SCHEME)), ['http', 'https'], true) ? $absolute : null;
|
||||
} catch (\InvalidArgumentException) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function reading(object $resource, ?array $override = null, bool $private = false): array
|
||||
{
|
||||
$original = $this->original($resource);
|
||||
$content = $override ?? $this->content($resource, $original);
|
||||
abort_unless(trim($content['body'] ?? '') !== '', 404, 'Este documento não contém um artigo legível.');
|
||||
$document = DB::table('documents')->find($resource->document_id);
|
||||
// The original is verified above; ordinary text adaptations retain the binary gate too.
|
||||
app(VerifiedDocumentBinary::class)->respond($document);
|
||||
$mode = $content['image_mode'] ?? (! empty($content['image_resource_id']) ? 'selected' : 'none');
|
||||
$originalImageUrl = $this->sourceUrl($original['image_source_url'] ?? null, $original['source_url'] ?? null);
|
||||
$expectedImageSha = $original['image_sha256'] ?? null;
|
||||
if (! $expectedImageSha && $originalImageUrl === 'https://investemoz.tempo.co.mz/images/tempo-background.png') {
|
||||
foreach (json_decode(file_get_contents(config_path('legacy_editorial_resources.json')), true, 512, JSON_THROW_ON_ERROR) as $accepted) {
|
||||
if ($accepted['sha256'] === $document->sha256) {
|
||||
$expectedImageSha = $accepted['original_image_sha256'] ?? null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$images = $this->publicResources()->whereIn('d.media_type', ['image/png', 'image/jpeg', 'image/webp']);
|
||||
$image = null;
|
||||
if ($mode === 'selected' && ! empty($content['image_resource_id'])) {
|
||||
$image = (clone $images)->where('r.id', $content['image_resource_id'])->first(['r.id', 'r.title']);
|
||||
}
|
||||
if ($mode === 'original' && $original) {
|
||||
$image = (clone $images)->where(function ($q) use ($expectedImageSha, $originalImageUrl) {
|
||||
$q->whereRaw('1=0');
|
||||
if ($expectedImageSha) {
|
||||
$q->orWhere('d.sha256', $expectedImageSha);
|
||||
} elseif ($originalImageUrl) {
|
||||
$q->orWhere('r.source_url', $originalImageUrl)->orWhere('d.original_source', $originalImageUrl);
|
||||
}
|
||||
})->first(['r.id', 'r.title']);
|
||||
}
|
||||
if ($image) {
|
||||
// A public flag alone never licenses missing or changed image bytes.
|
||||
$binary = (clone $images)->where('r.id', $image->id)->first(['d.*']);
|
||||
try {
|
||||
app(VerifiedDocumentBinary::class)->respond($binary);
|
||||
} catch (HttpException) {
|
||||
$image = null;
|
||||
}
|
||||
}
|
||||
$originalFormat = $original && ($content['preserve_original_html'] ?? false) && $content['body'] === $this->plain($original['content_html']);
|
||||
$html = $originalFormat ? app(LegacyContent::class)->sanitize($original['content_html'], true) : null;
|
||||
$imageReferences = [];
|
||||
if ($original) {
|
||||
preg_match_all('~<img\b[^>]*\bsrc\s*=\s*([\'"])(.*?)\1~is', $original['content_html'], $matches);
|
||||
foreach ($matches[2] as $source) {
|
||||
$url = $this->sourceUrl(html_entity_decode($source, ENT_QUOTES | ENT_HTML5, 'UTF-8'), $original['source_url'] ?? null);
|
||||
if ($url) {
|
||||
$imageReferences[] = $url;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ['id' => $resource->id, 'title' => $resource->title, 'description' => $resource->description,
|
||||
'author' => $document->issuer, 'body' => $content['body'], 'html' => $html,
|
||||
'image_url' => $image ? '/documentos/'.$image->id.'/view' : null,
|
||||
'image_notice' => $mode === 'selected' && ! $image ? 'A imagem escolhida está indisponível para divulgação; a selecção mantém-se na edição.' : ($mode === 'original' && ! $image && ! empty($original['image_source_url']) ? 'Imagem de origem referenciada, mas sem exemplar público verificado na biblioteca. A referência original mantém-se preservada.' : null),
|
||||
'source_url' => $this->sourceUrl($original['source_url'] ?? $resource->source_url),
|
||||
'published_at' => $original['published_at'] ?? null, 'modified_at' => $original['modified_at'] ?? null,
|
||||
'legacy_id' => $original['legacy_id'] ?? null, 'image_source_url' => $originalImageUrl, 'image_expected_sha256' => $expectedImageSha, 'image_references' => array_values(array_unique($imageReferences)),
|
||||
'metadata_version' => $resource->metadata_version, 'private_preview' => $private,
|
||||
'original_url' => $private ? '/management/documents/'.$resource->document_id.'/download' : '/documentos/'.$resource->id,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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'] : [])],
|
||||
@@ -231,16 +231,25 @@ class InvestmentCatalogue
|
||||
} elseif (in_array($relation, ['clusters', 'corridors'], true)) {
|
||||
$query->whereIn('id', $this->publicStructureIds($relation, $foreign));
|
||||
} else {
|
||||
$query->whereIn('id', InvestmentPortfolioController::visible()->whereNotNull($foreign)->pluck($foreign));
|
||||
$query->whereIn('id', app(OpportunityClassification::class)->targets('project')->select('id'));
|
||||
}
|
||||
}
|
||||
if (! $management && $path !== [] && $relation !== 'locations') {
|
||||
$dossiers = InvestmentPortfolioController::visible();
|
||||
app(InvestmentGeography::class)->constrainDossiersToTerritories($dossiers,
|
||||
app(InvestmentGeography::class)->descendants($path[array_key_last($path)]->id));
|
||||
$query->whereIn('id', $dossiers->whereNotNull($foreign)->select($foreign));
|
||||
$territories = app(InvestmentGeography::class)->descendants($path[array_key_last($path)]->id);
|
||||
$query->where(fn ($area) => $area->whereIn('primary_territory_id', $territories)
|
||||
->orWhereIn('id', $dossiers->whereNotNull($foreign)->select($foreign)));
|
||||
}
|
||||
$result[$relation] = $query->orderBy('name')->get(['id', 'name']);
|
||||
if (! $management && $relation !== 'locations') {
|
||||
$dossiers = InvestmentPortfolioController::visible()->whereIn($foreign, $result[$relation]->pluck('id'))
|
||||
->orderBy('id')->get(['id', $foreign])->unique($foreign)->keyBy($foreign);
|
||||
$result[$relation]->each(function ($record) use ($dossiers): void {
|
||||
$record->url = isset($dossiers[$record->id]) ? '/carteira/'.$dossiers[$record->id]->id : null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$zones = app(InvestmentZones::class)->visible();
|
||||
@@ -250,24 +259,15 @@ class InvestmentCatalogue
|
||||
['province_id', 'district_id', 'admin_post_id', 'locality_id', 'province', 'district', 'geo_location_id']));
|
||||
}
|
||||
$result['zones'] = $zones->orderBy('name')->get(['id', 'name', 'category']);
|
||||
if (! $management && ($path !== [] || ! empty($territoryFilters['sector']))) {
|
||||
$scope = \Illuminate\Support\Arr::only($territoryFilters, ['sector','province_id','district_id','admin_post_id','locality_id','include_national']);
|
||||
$available = ['chain' => [], 'activity' => []];
|
||||
$this->query($scope)->reorder()->chunkById(200, function ($records) use (&$available) {
|
||||
foreach ($records as $record) {
|
||||
foreach (app(OpportunityClassification::class)->publicRows($record) as $row) {
|
||||
if (isset($available[$row['target_type']])) $available[$row['target_type']][] = $row['target_id'];
|
||||
}
|
||||
foreach (['subsector_id','economic_activity_id'] as $key) {
|
||||
$id = $record->workflow_context['publication']['taxonomy'][$key] ?? null;
|
||||
if ($id) $available['activity'][] = $id;
|
||||
}
|
||||
}
|
||||
}, 'opportunities.id', 'id');
|
||||
foreach ($available as $kind => $ids) {
|
||||
$result['classification']['targets'][$kind] = collect($result['classification']['targets'][$kind])->whereIn('id', $ids)->values();
|
||||
}
|
||||
if (! $management && $path !== []) {
|
||||
// A chain's documented scope comes from its public cluster, never
|
||||
// from the opportunities surviving the current search.
|
||||
$ids = app(OpportunityClassification::class)->targets('chain')
|
||||
->whereIn('cluster_id', $result['clusters']->pluck('id'))->pluck('id');
|
||||
$result['classification']['targets']['chain'] = collect($result['classification']['targets']['chain'])->whereIn('id', $ids)->values();
|
||||
}
|
||||
// Activities are a public classification catalogue, not evidence of
|
||||
// investment availability in a territory or sector.
|
||||
|
||||
return $result;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ namespace App\Services;
|
||||
use App\Models\ExistingBusiness;
|
||||
use App\Models\Opportunity;
|
||||
use App\Models\Sector;
|
||||
use App\Models\SimulationFormDraft;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Support\Arr;
|
||||
@@ -12,6 +13,7 @@ use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
|
||||
/** Context belongs to the existing encrypted dossier. Sources remain read-only. */
|
||||
@@ -21,10 +23,69 @@ final class InvestorContext
|
||||
|
||||
public const SOURCE = 'investor-context';
|
||||
|
||||
public const FIELDS = ['project_title', 'project_summary', 'sector_id', 'sector', 'province_id', 'district_id', 'admin_post_id', 'locality_id', 'province', 'district', 'location'];
|
||||
public const FIELDS = ['project_title', 'project_summary', 'sector_id', 'sector', 'province_id', 'district_id', 'admin_post_id', 'locality_id', 'province', 'district', 'location', 'currency', 'expected_start_date', 'fiscal_period', 'fiscal_periodicity', 'fiscal_month', 'investor_origin', 'person_type', 'entity_stage', 'tax_residency_country', 'fiscal_residency_country', 'activity_id', 'project_type', 'maturity_stage', 'special_zone_id', 'investment_regime', ...PrivateInvestmentPreparation::FIELDS];
|
||||
|
||||
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');
|
||||
@@ -34,12 +95,31 @@ final class InvestorContext
|
||||
{
|
||||
$payload = $this->desk->decode($case->payload);
|
||||
|
||||
return $payload['investor_path']['context'] ?? ['revision' => 0, 'selection' => [],
|
||||
$context = $payload['investor_path']['context'] ?? ['revision' => 0, 'selection' => [],
|
||||
'fields' => ['project_title' => $case->title], 'provenance' => ['project_title' => ['kind' => 'dossier', 'id' => $case->id, 'version' => $case->version]], 'review_steps' => []];
|
||||
// Explicit dossier anchor only: never the account's most recent simulation or another owner's draft.
|
||||
if (($case->simulation_id ?? null) && request()->user()?->id === (int) $case->user_id) {
|
||||
$draft = SimulationFormDraft::whereKey($case->simulation_id)->where('owner_user_id', $case->user_id)->whereNull('deleted_at')->first();
|
||||
if ($draft) {
|
||||
$values = [...SimulationFormDraft::decryptSnapshot($draft->personal_snapshot), ...SimulationFormDraft::decryptSnapshot($draft->economic_snapshot)];
|
||||
foreach (Arr::only($values, self::FIELDS) as $key => $value) {
|
||||
if (is_scalar($value) && (string) $value !== '' && (! array_key_exists($key, $context['fields']) || ($context['provenance'][$key]['kind'] ?? null) === 'linked_simulation')) {
|
||||
$context['fields'][$key] = (string) $value;
|
||||
$context['provenance'][$key] = ['kind' => 'linked_simulation', 'id' => $draft->id, 'version' => $draft->version_no, 'sha256' => $draft->snapshot_sha256];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$zone = $context['selection']['park'] ?? null;
|
||||
$context['fields']['investment_regime'] = $zone['fields']['investment_regime'] ?? 'general';
|
||||
$context['fields']['special_zone_id'] = $zone['id'] ?? '';
|
||||
|
||||
return $context;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -53,17 +133,20 @@ final class InvestorContext
|
||||
$documents = [];
|
||||
$visibility = 'public';
|
||||
$state = null;
|
||||
$zoneAnalysis = null;
|
||||
if ($kind === 'opportunity') {
|
||||
abort_unless(ctype_digit($id), 422, 'Identificador de oportunidade inválido.');
|
||||
$o = Opportunity::with('sector')->findOrFail((int) $id);
|
||||
$published = Opportunity::published()->whereKey($o->id)->exists();
|
||||
if (!$published) {
|
||||
if (! $published) {
|
||||
abort_unless(request()->user() && $o->status !== 'archived' && app(PortalProfileAccess::class)->canView(request()->user(), $o), 404, 'Ficha privada indisponível neste âmbito.');
|
||||
$visibility = 'private';
|
||||
$state = app(PortalProfileAccess::class)->state($o);
|
||||
}
|
||||
if (request()->user()) $documents = app(OpportunityLibrarySupport::class)->snapshot(request()->user(), $o);
|
||||
$draft = !$published ? app(OpportunityManagement::class)->values($o) : [];
|
||||
if (request()->user()) {
|
||||
$documents = app(OpportunityLibrarySupport::class)->snapshot(request()->user(), $o);
|
||||
}
|
||||
$draft = ! $published ? app(OpportunityManagement::class)->values($o) : [];
|
||||
$title = $draft['canonical']['official_title'] ?? $o->title;
|
||||
$href = $published ? '/oportunidades/'.$o->slug : '/minhas-oportunidades/'.$o->id.'/view';
|
||||
$version = $o->management_version;
|
||||
@@ -122,19 +205,28 @@ final class InvestorContext
|
||||
$fields = $this->territoryFields($id);
|
||||
} elseif ($kind === 'park') {
|
||||
abort_unless(Str::isUuid($id), 422);
|
||||
$p = app(InvestmentZones::class)->visible()->where('category', 'industrial_park')->findOrFail($id);
|
||||
$p = app(InvestmentZones::class)->visible()->whereIn('category', ['industrial_park', 'zee', 'zfi'])->findOrFail($id);
|
||||
$title = $p->name;
|
||||
$fields = ['special_zone_id' => (string) $p->id, 'investment_regime' => (string) $p->category];
|
||||
$version = $p->version_no;
|
||||
$zoneAnalysis = ['conditions' => app(InvestmentZoneOperationalDetails::class)->publicData($p)['conditions'] ?? [],
|
||||
'source' => $p->legal_source_id ? DB::table('investment_legal_sources')->where('id', $p->legal_source_id)->first(['title', 'url', 'diploma', 'article', 'valid_from', 'valid_until']) : null,
|
||||
'recognition' => 'undetermined', 'executable_benefit' => false];
|
||||
$href = '/zonas/'.$id;
|
||||
if ($p->primary_territory_id) {
|
||||
$fields = $this->territoryFields($p->primary_territory_id);
|
||||
$fields = [...$fields, ...$this->territoryFields($p->primary_territory_id)];
|
||||
}
|
||||
} else {
|
||||
abort(422, 'Tipo de referência inválido.');
|
||||
}
|
||||
$fields = array_filter($fields, fn ($v) => is_scalar($v) && trim((string) $v) !== '');
|
||||
$record = ['key' => $key, 'kind' => $kind, 'id' => $id, 'title' => $title, 'version' => $version, 'href' => $href, 'fields' => $fields];
|
||||
if ($documents || $visibility === 'private') $record += ['documents' => $documents, 'visibility' => $visibility, 'state' => $state];
|
||||
if ($zoneAnalysis !== null) {
|
||||
$record['analysis'] = $zoneAnalysis;
|
||||
}
|
||||
if ($documents || $visibility === 'private') {
|
||||
$record += ['documents' => $documents, 'visibility' => $visibility, 'state' => $state];
|
||||
}
|
||||
|
||||
return [...$record, 'sha256' => hash('sha256', json_encode($record, JSON_THROW_ON_ERROR))];
|
||||
}
|
||||
@@ -170,7 +262,11 @@ final class InvestorContext
|
||||
}
|
||||
if (request()->user()) {
|
||||
$private = app(PortalProfileAccess::class)->scope(Opportunity::query(), request()->user())->where('status', '!=', 'published')->where('status', '!=', 'archived')->when($query, fn ($q) => $q->where('title', 'ilike', '%'.$query.'%'))->orderBy('title')->limit(51)->get();
|
||||
foreach ($private as $o) if (app(PortalProfileAccess::class)->canView(request()->user(), $o)) $items[] = ['key' => 'opportunity:'.$o->id, 'title' => $o->title.' — preparação privada', 'kind' => 'opportunity'];
|
||||
foreach ($private as $o) {
|
||||
if (app(PortalProfileAccess::class)->canView(request()->user(), $o)) {
|
||||
$items[] = ['key' => 'opportunity:'.$o->id, 'title' => $o->title.' — preparação privada', 'kind' => 'opportunity'];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (config('existing-business.enabled')) {
|
||||
foreach (ExistingBusiness::published()->when($query, fn ($q) => $q->where('public_title', 'ilike', '%'.$query.'%'))->orderBy('public_title')->limit(51)->get(['id', 'public_title']) as $o) {
|
||||
@@ -179,7 +275,7 @@ final class InvestorContext
|
||||
}
|
||||
|
||||
return ['opportunities' => $items, 'search_limited' => count($items) > 50, 'sectors' => Sector::choices()->orderBy('name')->get(['id', 'name', 'slug']),
|
||||
'parks' => app(InvestmentZones::class)->visible()->where('category', 'industrial_park')->orderBy('name')->get(['id', 'name', 'version_no'])];
|
||||
'parks' => app(InvestmentZones::class)->visible()->whereIn('category', ['industrial_park', 'zee', 'zfi'])->orderBy('name')->get(['id', 'name', 'category', 'version_no'])];
|
||||
}
|
||||
|
||||
public function current(User $actor): array
|
||||
@@ -232,10 +328,28 @@ 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', '_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.');
|
||||
|
||||
return Validator::make($input, ['version' => 'required|integer|min:1', 'selection' => 'present|array:opportunity,sector,territory,park',
|
||||
'selection.*' => 'nullable|string|max:100', 'declarations' => 'nullable|array:project_title,project_summary', 'declarations.project_title' => 'nullable|string|max:200', 'declarations.project_summary' => 'nullable|string|max:5000',
|
||||
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.');
|
||||
|
||||
$inputRules = [
|
||||
'document_links' => 'sometimes|array:'.implode(',', PrivateInvestmentPreparation::DOCUMENTS),
|
||||
'document_links.*' => 'nullable|uuid',
|
||||
];
|
||||
foreach (app(PrivateInvestmentPreparation::class)->questions() as $question) {
|
||||
$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,
|
||||
'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();
|
||||
}
|
||||
|
||||
@@ -247,6 +361,32 @@ final class InvestorContext
|
||||
abort_unless((int) $case->version === $data['version'], 409, 'O dossier mudou; recupere a versão atual.');
|
||||
app(InvestorPath::class)->view($actor, $id);
|
||||
$old = $this->state($case);
|
||||
$links = $old['preparation_documents'] ?? [];
|
||||
foreach ($data['document_links'] ?? [] as $key => $document) {
|
||||
if ($document) {
|
||||
abort_unless(DB::table('desk_documents as l')->join('documents as d', 'd.id', '=', 'l.document_id')->where('l.case_id', $id)->where('d.id', $document)->whereNull('d.deleted_at')->exists(), 422, 'Seleccione um documento já associado a este dossier; os dados anteriores foram preservados.');
|
||||
$links[$key] = $document;
|
||||
} else {
|
||||
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 === '') {
|
||||
@@ -259,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()) {
|
||||
@@ -299,10 +449,20 @@ final class InvestorContext
|
||||
}
|
||||
// Remove inherited subordinate geography only when its source was actually replaced.
|
||||
foreach ($fields as $key => $value) {
|
||||
if (! array_key_exists($key, $proposed) && in_array($key, ['province_id', 'district_id', 'admin_post_id', 'locality_id', 'province', 'district', 'location', 'sector_id', 'sector'], true) && isset($provenance[$key]['key'])) {
|
||||
if (! array_key_exists($key, $proposed) && in_array($key, ['province_id', 'district_id', 'admin_post_id', 'locality_id', 'province', 'district', 'location', 'sector_id', 'sector', 'special_zone_id', 'investment_regime'], true) && isset($provenance[$key]['key'])) {
|
||||
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)) {
|
||||
@@ -314,15 +474,38 @@ final class InvestorContext
|
||||
$changed[] = $slot;
|
||||
}
|
||||
}
|
||||
$dependencyChanged = $this->dependencyHash($old['selection']) !== $this->dependencyHash($selection);
|
||||
$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 = $choiceInputsChanged || $this->dependencyHash($old['selection']) !== $this->dependencyHash($selection) || $conditionsChanged;
|
||||
$privateConditionsChanged = (bool) array_intersect($changedFields, PrivateInvestmentPreparation::FIELDS);
|
||||
$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();
|
||||
$steps = $dependencyChanged ? array_values(array_unique([...(array_intersect($changed, ['opportunity', 'sector']) ? ['S02', 'S03', 'S04', 'S08', 'S12', 'S13', 'S14', 'S15', 'S17'] : []), ...(array_intersect($changed, ['territory', 'park']) ? ['S08', 'S09', 'S10', 'S11', 'S17'] : [])])) : [];
|
||||
$plan = ['case_id' => $id, 'version' => (int) $case->version, 'revision' => (int) $old['revision'], 'selection' => $selection, 'fields' => $fields, 'provenance' => $provenance,
|
||||
'updated' => $replaced, 'maintained' => $kept, 'changed_dimensions' => $changed, 'dependent_results' => (int) $case->user_id === $actor->id && $dependencyChanged ? array_map(fn ($r) => ['id' => $r['id'], 'engine' => $r['engine']], $results) : [],
|
||||
'requests' => $requests, 'review_steps' => $steps, 'dependency_changed' => $dependencyChanged, 'documents_preserved' => DB::table('desk_documents')->where('case_id', $id)->count(),
|
||||
if (array_intersect($changedFields, array_diff(self::FIELDS, ['project_title', 'project_summary', ...PrivateInvestmentPreparation::FIELDS]))) {
|
||||
$steps = array_values(array_unique([...$steps, 'S02', 'S03', 'S08', 'S12', 'S13', 'S17']));
|
||||
}
|
||||
if ($privateConditionsChanged) {
|
||||
$steps = array_values(array_unique([...$steps, 'S02', 'S05', 'S06', 'S07']));
|
||||
}
|
||||
$privatePreparation = null;
|
||||
if (app(PrivateInvestmentPreparation::class)->available()) {
|
||||
$docs = $this->preparationDocuments($id);
|
||||
$beforePreparation = app(PrivateInvestmentPreparation::class)->assess($old, $docs);
|
||||
$afterPreparation = app(PrivateInvestmentPreparation::class)->assess(['fields' => $fields, 'selection' => $selection, 'preparation_documents' => $links], $docs);
|
||||
$privatePreparation = ['before' => $beforePreparation, 'after' => $afterPreparation];
|
||||
}
|
||||
$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];
|
||||
@@ -349,7 +532,7 @@ final class InvestorContext
|
||||
$before = $this->state($case);
|
||||
$plan = $this->preview($actor, $id, $input);
|
||||
abort_unless(hash_equals($plan['preview_hash'], $control['preview_hash']), 409, 'O dossier ou uma fonte mudou desde a prévia; compare novamente.');
|
||||
if (! $plan['changed_dimensions'] && ! $plan['updated'] && empty($input['declarations']) && empty($input['replace_fields'])) {
|
||||
if (! $plan['changed_dimensions'] && ! $plan['updated'] && empty($input['declarations']) && empty($input['replace_fields']) && empty($input['document_links'])) {
|
||||
$result = ['case_id' => $id, 'version' => (int) $case->version, 'revision' => $before['revision'], 'changed' => false, 'replayed' => false];
|
||||
DB::table('audit_events')->insert(['source_system' => self::SOURCE, 'entity_type' => 'desk_cases', 'entity_id' => $id, 'actor_user_id' => $actor->id, 'correlation_id' => $control['operation_key'], 'action' => 'context_unchanged', 'data_classification' => 'restricted', 'after_data' => json_encode(['case_id' => $id, 'request_hash' => $requestHash, 'result' => $result], JSON_THROW_ON_ERROR)]);
|
||||
|
||||
@@ -361,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 = ['revision' => $revision, 'selection' => $plan['selection'], 'fields' => $plan['fields'], 'provenance' => $plan['provenance'], 'review_steps' => $review,
|
||||
'dependency_hash' => $this->dependencyHash($plan['selection']), 'updated_at' => now()->toIso8601String(), 'updated_by' => $actor->id];
|
||||
if ($plan['dependency_changed']) {
|
||||
$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];
|
||||
@@ -383,7 +568,10 @@ 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']))))];
|
||||
|
||||
$simulatorRevision = null;
|
||||
if ((int) $case->user_id === $actor->id && $case->simulation_id) {
|
||||
@@ -396,18 +584,62 @@ final class InvestorContext
|
||||
}
|
||||
}
|
||||
|
||||
return ['simulator_context_revision' => $simulatorRevision, 'case' => $path['case'], 'context' => $ctx, 'steps' => array_map(fn ($s) => Arr::only($s, ['code', 'title', 'state', 'context_review_required']), $path['steps']),
|
||||
$preparation = app(PrivateInvestmentPreparation::class)->available() ? app(PrivateInvestmentPreparation::class)->assess($ctx, $this->preparationDocuments($id)) : null;
|
||||
$ctx['preparation_assessment'] = $preparation;
|
||||
|
||||
return ['private_investment' => $preparation, 'preparation_documents' => $this->preparationDocuments($id), 'simulator_context_revision' => $simulatorRevision, 'case' => $path['case'], 'context' => $ctx, 'steps' => array_map(fn ($s) => Arr::only($s, ['code', 'title', 'state', 'context_review_required']), $path['steps']),
|
||||
'can_write' => $path['canWrite'], 'next' => $path['next'], 'results' => $path['results'], 'requests' => $path['requests'],
|
||||
'prefill' => $this->prefill($ctx), 'notice' => 'A seleção apoia a preparação e não confirma benefícios, licenças, direitos ou disponibilidade.'];
|
||||
}
|
||||
|
||||
public function preparationDocuments(string $id): array
|
||||
{
|
||||
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::only($f, ['project_title', 'project_summary', 'sector_id', 'province_id', 'district_id', 'admin_post_id', 'locality_id']);
|
||||
$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.',
|
||||
], JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
|
||||
|
||||
return ['simulator' => $sim, 'land' => array_filter(['project_reference' => $f['project_title'] ?? null, 'activity' => $f['sector'] ?? null, 'purpose' => $f['project_summary'] ?? null, 'parcels' => [Arr::only($f, array_keys(InvestmentGeography::FIELDS))]], fn ($v) => $v !== null),
|
||||
'desk' => array_filter(['title' => $f['project_title'] ?? null, 'territory_id' => $f['locality_id'] ?? $f['admin_post_id'] ?? $f['district_id'] ?? $f['province_id'] ?? null, 'location_source' => isset($f['province_id']) ? 'Referência consultada no contexto v'.($ctx['revision'] ?? 0).': '.($f['location'] ?? 'território identificado').'. Não confirma disponibilidade.' : null, 'description' => $f['project_summary'] ?? null, 'sector' => $f['sector'] ?? null, ...Arr::only($f, array_keys(InvestmentGeography::FIELDS))], fn ($v) => $v !== null),
|
||||
'submission' => array_filter(['sector' => $f['sector'] ?? null, 'objective' => $f['project_summary'] ?? null, 'location' => $f['location'] ?? null], fn ($v) => $v !== null)];
|
||||
'submission' => array_filter(['currency' => $f['currency'] ?? null, 'sector' => $f['sector'] ?? null, 'objective' => $f['project_summary'] ?? null, 'location' => $f['location'] ?? null], fn ($v) => $v !== null)];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ final class InvestorPath
|
||||
$documents = DB::table('desk_documents as l')->join('documents as d', 'd.id', '=', 'l.document_id')
|
||||
->where('l.case_id', $id)->whereNull('d.deleted_at')
|
||||
->when(! $owner && ! $manager, fn ($q) => $q->where(fn ($p) => $p->whereNull('l.request_id')->orWhereIn('l.request_id', $requests->pluck('id'))))
|
||||
->get(['l.id', 'l.document_id', 'd.file_name', 'd.status', 'd.malware_scan_status']);
|
||||
->get(['l.id', 'l.document_id', 'd.file_name', 'd.status', 'd.malware_scan_status', 'd.version_no', 'd.sha256']);
|
||||
// Only the private draft owner can receive the financial snapshot/result. Dossier access alone is insufficient.
|
||||
$results = (int) $case->user_id === $actor->id ? ($path['results'] ?? []) : [];
|
||||
$next = collect($steps)->first(fn ($step) => $step['preparation_allowed'] && ! in_array($step['state'], ['completed', 'approved', 'waiting_external', 'submitted', 'reviewing'], true));
|
||||
@@ -145,12 +145,17 @@ final class InvestorPath
|
||||
? LandAssistanceCase::where('user_id', $actor->id)->latest('updated_at')->get()
|
||||
->first(fn ($land) => ($land->payload['investor_path_case_id'] ?? null) === $id) : null;
|
||||
|
||||
return ['case' => ['id' => $id, 'title' => $case->title, 'version' => $case->version, 'closed' => $case->closed_at !== null],
|
||||
$preparation = ($owner || $manager) && app(PrivateInvestmentPreparation::class)->available() ? app(PrivateInvestmentPreparation::class)->assess($context, $documents->map(fn ($d) => (array) $d)->all()) : null;
|
||||
if ($preparation && $preparation['enabled']) {
|
||||
$next = ['code' => 'S06', 'title' => $preparation['next'], 'action' => 'Preparar', 'href' => '/investimentos/'.$id.'/contexto/editar#preparation-questions'];
|
||||
}
|
||||
|
||||
return ['private_investment' => $preparation, 'case' => ['id' => $id, 'title' => $case->title, 'version' => $case->version, 'closed' => $case->closed_at !== null],
|
||||
'journey_id' => $journey->id, 'definition_id' => $version->id, 'definition_version' => $version->version_no, 'source_sha256' => $definition['source_sha256'],
|
||||
'phases' => $definition['phases'], 'steps' => $steps, 'facts' => $facts, 'fact_reasons' => $path['fact_reasons'] ?? [],
|
||||
'fintech' => ($owner || $manager) && (isset($path['fintech']) || collect($path['sector_contexts'] ?? [])->contains(fn ($s) => in_array($s['slug'], ['fintech', 'tecnologias-de-informacao', 'mercado-financeiro'], true)) || collect($path['content_sources'] ?? [])->contains('return_href', '/tech-fintech')) ? app(FintechInvestment::class)->view($payload, $steps) : null,
|
||||
'canWrite' => $owner && ! $case->closed_at, 'canReview' => $manager && (int) $case->user_id !== $actor->id && ! $case->closed_at,
|
||||
'next' => $next ? ['code' => $next['code'], 'title' => $next['title'], 'action' => 'Preparar', 'href' => '#'.$next['code']] : null,
|
||||
'next' => $next ? ['code' => $next['code'], 'title' => $next['title'], 'action' => 'Preparar', 'href' => $next['href'] ?? '#'.$next['code']] : null,
|
||||
'investment_context' => ($owner || $manager) ? $context : null,
|
||||
'results' => $results, 'requests' => $requests, 'documents' => $documents,
|
||||
'zone_contexts' => ($owner || $manager) ? ($path['zone_contexts'] ?? []) : [],
|
||||
@@ -165,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 */
|
||||
|
||||
@@ -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 = [];
|
||||
@@ -279,7 +282,7 @@ final class LegacyContent
|
||||
}
|
||||
|
||||
/** Allow presentation markup only. No scripts, styles, events, embeds or remote image execution. */
|
||||
public function sanitize(string $html): string
|
||||
public function sanitize(string $html, bool $preserveStructure = false): string
|
||||
{
|
||||
$doc = new DOMDocument;
|
||||
$old = libxml_use_internal_errors(true);
|
||||
@@ -296,7 +299,7 @@ final class LegacyContent
|
||||
}
|
||||
$headingOffset = 2 - (empty($sourceLevels) ? 2 : min($sourceLevels));
|
||||
$previousHeading = 1;
|
||||
$clean = function (DOMNode $node) use (&$clean, $doc, $headingOffset, &$previousHeading): void {
|
||||
$clean = function (DOMNode $node) use (&$clean, $doc, $headingOffset, &$previousHeading, $preserveStructure): void {
|
||||
foreach (iterator_to_array($node->childNodes) as $child) {
|
||||
if (! $child instanceof DOMElement) {
|
||||
continue;
|
||||
@@ -311,7 +314,7 @@ final class LegacyContent
|
||||
$node->replaceChild($section, $child);
|
||||
$child = $section;
|
||||
}
|
||||
if (! in_array(strtolower($child->tagName), ['p', 'strong', 'b', 'em', 'i', 'ul', 'ol', 'li', 'br', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'a'], true)) {
|
||||
if (! in_array(strtolower($child->tagName), array_merge(['p', 'strong', 'b', 'em', 'i', 'ul', 'ol', 'li', 'br', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'a'], $preserveStructure ? ['div', 'span', 'small', 'section', 'table', 'caption', 'thead', 'tbody', 'tfoot', 'tr', 'th', 'td', 'figure', 'figcaption'] : []), true)) {
|
||||
$node->removeChild($child);
|
||||
|
||||
continue;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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)))];
|
||||
|
||||
@@ -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.'];
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -184,7 +184,9 @@ class PortalProfileAccess
|
||||
|
||||
public function canCreate(User $actor, ?string $organization, string $type): bool
|
||||
{
|
||||
$included = in_array($type, OpportunityCanonicalContract::BASE_CATEGORIES, true) || app(OpportunityModularProfiles::class)->enabled();
|
||||
// Draft preparation is allowed within the assigned modality. Executable
|
||||
// submission support is checked separately by the canonical contract.
|
||||
$included = in_array($type, OpportunityManagement::CATEGORIES, true);
|
||||
|
||||
// This is creation as an authorized Promotor, never an exceptional SU act.
|
||||
return $included && $this->available() && $this->scoped($actor, 'promoter', $organization, $type) && ! $this->has($actor, 'auditor');
|
||||
@@ -370,7 +372,8 @@ class PortalProfileAccess
|
||||
foreach (json_decode($grant->opportunity_types, true, 512, JSON_THROW_ON_ERROR) as $type) {
|
||||
$key = ($grant->organization_id ?? 'individual').'|'.$type;
|
||||
if (! isset($seen[$key]) && $this->canCreate($actor, $grant->organization_id, $type)) {
|
||||
$contexts[] = ['organization_id' => $grant->organization_id, 'type' => $type];
|
||||
$contexts[] = ['organization_id' => $grant->organization_id,
|
||||
'organization_name' => $grant->organization_id ? DB::table('organizations')->where('id', $grant->organization_id)->value('name') : null, 'type' => $type];
|
||||
$seen[$key] = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Brick\Math\BigDecimal;
|
||||
|
||||
/** Adapter of the dossier's declared context to preparation of a private-investment request. No tax or entitlement decision. */
|
||||
final class PrivateInvestmentPreparation
|
||||
{
|
||||
public const VERSION = 'private-investment-preparation-2026-v1';
|
||||
|
||||
public const SOURCE = 'https://pt.apiex.gov.mz/wp-content/uploads/2025/02/LEGISLACAO-DE-INVESTIMENTOS-EM-MOCAMBIQUE.pdf';
|
||||
|
||||
public const HASH = 'ba1e9eaabfec5d0c4cb2d9f2444a6860f7a90f222034764197829be402d408ed';
|
||||
|
||||
public const FIELDS = ['investment_form', 'investment_preparation', 'investment_activity_scope', 'implementation_mode', 'capital_origin', 'site_tenure', 'project_concession', 'significant_implications', 'land_extension', 'forest_concession', 'forest_concession_hectares', 'total_investment', 'area_required', 'area_unit'];
|
||||
|
||||
public const DOCUMENTS = ['identity', 'registration', 'name_reservation', 'site_plan', 'lease', 'duat', 'representation_license', 'feasibility', 'financial_capacity', 'foreign_capital'];
|
||||
|
||||
public function available(): bool
|
||||
{
|
||||
return (bool) config('investor-path.private_preparation', false);
|
||||
}
|
||||
|
||||
public function questions(): array
|
||||
{
|
||||
$tri = ['unknown' => 'Por determinar', 'yes' => 'Sim', 'no' => 'Não'];
|
||||
|
||||
return [
|
||||
['key' => 'investment_preparation', 'label' => 'Pretende preparar o pedido de garantias/incentivos ao investimento privado?', 'options' => $tri, 'source' => 'Lei 8/2023, artigo 2'],
|
||||
['key' => 'investment_activity_scope', 'label' => 'Natureza efectiva da actividade do projecto', 'options' => ['unknown' => 'Por determinar', 'general' => 'Outra actividade económica', 'extractive' => 'Prospecção, pesquisa ou produção extractiva', 'processing' => 'Processamento industrial mineiro/petrolífero', 'public_nonprofit' => 'Investimento público ou exclusivamente social/sem fins lucrativos'], 'source' => 'Lei 8/2023, artigos 2 e 22'],
|
||||
['key' => 'investment_form', 'label' => 'Tipo deste investimento', 'options' => ['unknown' => 'Por determinar', 'direct' => 'Directo', 'indirect' => 'Indirecto'], 'source' => 'Lei 8/2023, artigo 14(b); Regulamento, artigo 13', 'help' => 'O limiar monetário do artigo 13 aplica-se ao investimento directo. A forma indirecta não é convertida silenciosamente em directa.'],
|
||||
['key' => 'implementation_mode', 'label' => 'Modalidade da empresa implementadora', 'options' => ['unknown' => 'Por determinar', 'domestic_entity' => 'Empresa constituída/em constituição em Moçambique', 'foreign_representation' => 'Representação comercial estrangeira'], 'source' => 'Regulamento 8/2024, artigo 9(2)'],
|
||||
['key' => 'capital_origin', 'label' => 'Origem dos capitais/recursos deste investimento', 'options' => ['unknown' => 'Por determinar', 'national' => 'Nacionais', 'external' => 'Provenientes do exterior', 'mixed' => 'Mistos'], 'source' => 'Lei 8/2023, glossário; Regulamento, artigos 4–6'],
|
||||
['key' => 'site_tenure', 'label' => 'Fundamento declarado da utilização das instalações', 'options' => ['unknown' => 'Por determinar', 'lease' => 'Arrendamento', 'duat' => 'DUAT'], 'source' => 'Regulamento, artigos 9(1)(d) e 14(1)(d)'],
|
||||
['key' => 'project_concession', 'label' => 'O empreendimento é PPP ou concessão empresarial?', 'options' => $tri, 'source' => 'Lei 8/2023, artigo 22(1)(b)(ii)'],
|
||||
['key' => 'significant_implications', 'label' => 'Existem previsíveis implicações económicas, ambientais, de segurança ou saúde pública que exijam autorização?', 'options' => $tri, 'source' => 'Lei 8/2023, artigo 22(1)(b)(i)', 'help' => 'Declare o enquadramento conhecido; o nome do sector não responde a esta pergunta. Sem fundamento suficiente, mantenha por determinar.'],
|
||||
['key' => 'land_extension', 'label' => 'O projecto requer extensão de terra?', 'options' => $tri, 'source' => 'Lei 8/2023, artigo 22(1)(b)(iii)'],
|
||||
['key' => 'forest_concession', 'label' => 'O projecto requer concessão florestal?', 'options' => $tri, 'source' => 'Lei 8/2023, artigo 22(1)(b)(iv)'],
|
||||
['key' => 'forest_concession_hectares', 'label' => 'Área da concessão florestal (hectares)', 'type' => 'number', 'when' => ['field' => 'forest_concession', 'equals' => 'yes'], 'source' => 'Lei 8/2023, artigo 22(1)(b)(iv)'],
|
||||
['key' => 'total_investment', 'label' => 'Valor total do investimento na moeda do projecto', 'type' => 'number', 'source' => 'Regulamento, artigo 13(2)'],
|
||||
['key' => 'area_required', 'label' => 'Área de terra requerida', 'type' => 'number', 'when' => ['field' => 'land_extension', 'equals' => 'yes'], 'source' => 'Lei 8/2023, artigo 22(1)(b)(iii)'],
|
||||
['key' => 'area_unit', 'label' => 'Unidade da área requerida', 'options' => ['ha' => 'Hectares', 'm2' => 'Metros quadrados'], 'when' => ['field' => 'land_extension', 'equals' => 'yes'], 'source' => 'Conversão de unidade exacta; não é benefício ou condição adicional'],
|
||||
];
|
||||
}
|
||||
|
||||
private function number(array $f, string $key): ?BigDecimal
|
||||
{
|
||||
$value = $f[$key] ?? '';
|
||||
|
||||
return is_scalar($value) && preg_match('/^\d+(?:\.\d+)?$/D', (string) $value) ? BigDecimal::of((string) $value) : null;
|
||||
}
|
||||
|
||||
public function assess(array $context, array $documents = []): array
|
||||
{
|
||||
$f = $context['fields'] ?? [];
|
||||
$missing = [];
|
||||
$answer = function (string $key) use ($f, &$missing): ?bool {
|
||||
if (! in_array($f[$key] ?? '', ['yes', 'no'], true)) {
|
||||
$missing[] = $key;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return $f[$key] === 'yes';
|
||||
};
|
||||
$enabled = ($f['investment_preparation'] ?? '') === 'yes';
|
||||
$date = $f['expected_start_date'] ?? '';
|
||||
$period = preg_match('/^2026-\d{2}-\d{2}$/D', $date) === 1;
|
||||
$scope = $f['investment_activity_scope'] ?? 'unknown';
|
||||
$covered = in_array($scope, ['general', 'processing'], true);
|
||||
$mode = 'indeterminate';
|
||||
$reasons = [];
|
||||
if ($enabled) {
|
||||
if (! $date) {
|
||||
$missing[] = 'expected_start_date';
|
||||
}
|
||||
if ($scope === 'unknown' || $scope === '') {
|
||||
$missing[] = 'investment_activity_scope';
|
||||
}
|
||||
if (empty($f['currency'])) {
|
||||
$missing[] = 'currency';
|
||||
}
|
||||
if ($covered && $period) {
|
||||
$triggers = [];
|
||||
if ($scope === 'processing') {
|
||||
$triggers['processing'] = true;
|
||||
$reasons[] = 'Processamento industrial mineiro/petrolífero: Lei 8/2023, artigo 22(1)(b)(v).';
|
||||
}
|
||||
$amount = $this->number($f, 'total_investment');
|
||||
$triggers['amount'] = $amount && ($f['currency'] ?? '') === 'MZN' && ($f['investment_form'] ?? '') === 'direct' ? $amount->isGreaterThan('32000000000') : null;
|
||||
if (! in_array($f['investment_form'] ?? '', ['direct', 'indirect'], true)) {
|
||||
$missing[] = 'investment_form';
|
||||
}
|
||||
if (($f['investment_form'] ?? '') === 'indirect') {
|
||||
$reasons[] = 'Investimento indirecto: a classificação pela dimensão e o encaminhamento monetário não são determinados por este lote. As restantes condições documentadas continuam avaliadas.';
|
||||
}
|
||||
if (! $amount) {
|
||||
$missing[] = 'total_investment';
|
||||
}
|
||||
if (($f['currency'] ?? '') !== 'MZN') {
|
||||
$reasons[] = 'A condição monetária não é determinada noutra moeda: não se fez conversão.';
|
||||
}
|
||||
foreach (['project_concession', 'significant_implications'] as $key) {
|
||||
$triggers[$key] = $answer($key);
|
||||
}
|
||||
$land = $answer('land_extension');
|
||||
if ($land === true) {
|
||||
$area = $this->number($f, 'area_required');
|
||||
$unit = $f['area_unit'] ?? '';
|
||||
if (! $area) {
|
||||
$missing[] = 'area_required';
|
||||
}if (! in_array($unit, ['ha', 'm2'], true)) {
|
||||
$missing[] = 'area_unit';
|
||||
}
|
||||
$triggers['land_extension'] = $area && in_array($unit, ['ha', 'm2'], true) ? $area->isGreaterThanOrEqualTo($unit === 'ha' ? '10000' : '100000000') : null;
|
||||
} else {
|
||||
$triggers['land_extension'] = $land;
|
||||
}
|
||||
$forest = $answer('forest_concession');
|
||||
if ($forest === true) {
|
||||
$area = $this->number($f, 'forest_concession_hectares');
|
||||
if (! $area) {
|
||||
$missing[] = 'forest_concession_hectares';
|
||||
}$triggers['forest_concession'] = $area ? $area->isGreaterThan('100000') : null;
|
||||
} else {
|
||||
$triggers['forest_concession'] = $forest;
|
||||
}
|
||||
foreach (['amount' => 'Valor superior a 32 mil milhões MZN — R13(2), R21(2)(a)', 'project_concession' => 'PPP/concessão empresarial — Lei 8/2023, art. 22(1)(b)(ii), R21(2)(b)', 'significant_implications' => 'Implicações significativas declaradas — Lei 8/2023, art. 22(1)(b)(i), R21(1)', 'land_extension' => 'Extensão de terra igual/superior a 10 mil hectares — Lei 8/2023, art. 22(1)(b)(iii), R21(2)(c)', 'forest_concession' => 'Concessão florestal superior a 100 mil hectares — Lei 8/2023, art. 22(1)(b)(iv), R21(2)(d)'] as $condition => $description) {
|
||||
if (($triggers[$condition] ?? null) === true) {
|
||||
$reasons[] = $description.'.';
|
||||
}
|
||||
}
|
||||
$mode = in_array(true, $triggers, true) ? 'authorization' : (in_array(null, $triggers, true) ? 'indeterminate' : 'registration');
|
||||
$reasons[] = $mode === 'authorization' ? 'Foi identificada uma condição de autorização; as restantes condições desconhecidas não foram presumidas negativas.' : ($mode === 'registration' ? 'As condições de autorização deste lote foram declaradas negativas e o montante não ultrapassa o limite.' : 'Falta resolver pelo menos uma condição de autorização.');
|
||||
}
|
||||
foreach (['implementation_mode', 'capital_origin', 'site_tenure', 'entity_stage'] as $key) {
|
||||
if (empty($f[$key]) || $f[$key] === 'unknown') {
|
||||
$missing[] = $key;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($mode === 'authorization' && in_array(true, array_intersect_key($triggers, array_flip(['amount', 'project_concession', 'land_extension', 'forest_concession'])), true)) {
|
||||
$missing = array_values(array_diff($missing, ['investment_form', 'project_concession', 'significant_implications', 'land_extension', 'forest_concession', 'forest_concession_hectares', 'area_required', 'area_unit', 'total_investment']));
|
||||
}
|
||||
$base = $enabled && $covered && $period ? true : null;
|
||||
if (! $enabled || in_array($scope, ['extractive', 'public_nonprofit'], true) || ($date && ! $period)) {
|
||||
$base = false;
|
||||
}
|
||||
$rules = [
|
||||
['identity', 'Identificação do investidor', $base, 'S06', 'R9(1)(a) / R14(1)(a)', 41, 'Identificação do proponente do pedido.'],
|
||||
['registration', 'Certidão de registo comercial', $base === true ? (($f['entity_stage'] ?? '') === 'existing' ? true : (($f['entity_stage'] ?? '') === 'being_formed' ? false : null)) : $base, 'S05', 'R9(1)(b) / R14(1)(b)', 41, 'Alternativa para empresa já registada.'],
|
||||
['name_reservation', 'Reserva da denominação social', $base === true ? (($f['entity_stage'] ?? '') === 'being_formed' ? true : (($f['entity_stage'] ?? '') === 'existing' ? false : null)) : $base, 'S05', 'R9(1)(b) / R14(1)(b)', 41, 'Alternativa para empresa em constituição.'],
|
||||
['site_plan', 'Planta ou esboço do local', $base, 'S06', 'R9(1)(c) / R14(1)(c)', 42, 'Local de implantação identificado; não certifica disponibilidade.'],
|
||||
];
|
||||
foreach (['lease' => 'Contrato de arrendamento', 'duat' => 'Título DUAT'] as $key => $label) {
|
||||
$rules[] = [$key, $label, $base === true ? (($f['site_tenure'] ?? 'unknown') === 'unknown' ? null : ($f['site_tenure'] ?? '') === $key) : $base, 'S06', 'R9(1)(d) / R14(1)(d)', 42, 'Alternativa correspondente à utilização declarada das instalações.'];
|
||||
}
|
||||
$rules[] = ['representation_license', 'Licença de representação comercial', $base === true ? (($f['implementation_mode'] ?? 'unknown') === 'unknown' ? null : ($f['implementation_mode'] ?? '') === 'foreign_representation') : $base, 'S06', 'R9(2)', 42, 'Só para a modalidade representação comercial estrangeira; não decorre da nacionalidade.'];
|
||||
foreach (['feasibility' => 'Estudo de viabilidade técnica e económico-financeira', 'financial_capacity' => 'Comprovação da capacidade financeira'] as $key => $label) {
|
||||
$rules[] = [$key, $label, $base === true ? ($mode === 'indeterminate' ? null : $mode === 'authorization') : $base, 'S02', 'R14(1)–(2)', 45, 'Integra a preparação do pedido de autorização.'];
|
||||
}
|
||||
$rules[] = ['foreign_capital', 'Prova de realização/registo do aporte externo', $base === true ? (($f['capital_origin'] ?? 'unknown') === 'unknown' ? null : in_array($f['capital_origin'] ?? '', ['external', 'mixed'], true)) : $base, 'S06', 'R5(1)–(5)', 39, 'Preparação específica do aporte externo; formalidades cambiais concretas não calculadas.'];
|
||||
$links = $context['preparation_documents'] ?? [];
|
||||
$requirements = [];
|
||||
foreach ($rules as [$key,$label,$applies,$stage,$article,$page,$reason]) {
|
||||
$doc = isset($links[$key]) ? collect($documents)->firstWhere('document_id', $links[$key]) : null;
|
||||
$requirements[] = ['key' => $key, 'label' => $label, 'state' => $applies === false ? 'not_applicable' : ($applies === null ? 'indeterminate' : ($doc ? 'associated' : 'missing')), 'required' => $applies === true, 'document_id' => $links[$key] ?? null, 'document' => $doc, 'stage' => $stage, 'reason' => $reason, 'source' => ['reference' => $article, 'url' => self::SOURCE.'#page='.$page, 'sha256' => self::HASH, 'period' => '2026; Regulamento desde 07/03/2024'], 'legal_verification' => 'not_inferred'];
|
||||
}
|
||||
$authoritySource = ['reference' => 'R13(1)', 'url' => self::SOURCE.'#page=45'];
|
||||
$zone = $context['selection']['park'] ?? null;
|
||||
$amount = $this->number($f, 'total_investment');
|
||||
$authority = 'Por determinar neste lote';
|
||||
if ($base === true && $mode === 'registration' && $amount && ($f['currency'] ?? '') === 'MZN') {
|
||||
$special = in_array($zone['fields']['investment_regime'] ?? '', ['zee', 'zfi'], true);
|
||||
$authority = $special ? ($amount->isLessThanOrEqualTo('6500000000') ? 'APIEX — condicionado ao enquadramento ZEE/ZFI efectivamente reconhecido' : 'Ministro que superintende Finanças') : ($amount->isLessThanOrEqualTo('3500000000') ? 'Governador da Província' : ($amount->isLessThanOrEqualTo('6500000000') ? 'APIEX' : 'Ministro que superintende Finanças'));
|
||||
}
|
||||
if ($base === true && $mode === 'authorization') {
|
||||
$council = array_intersect_key($triggers, array_flip(['amount', 'project_concession', 'land_extension', 'forest_concession']));
|
||||
$authority = in_array(true, $council, true) ? 'Conselho de Ministros' : (in_array(null, $council, true) ? 'Autorização identificada; completar as condições em falta para distinguir a entidade decisória' : 'Ministro que superintende Finanças; eventual submissão ao Conselho de Ministros nos termos do R21(3)');
|
||||
$authoritySource = ['reference' => 'R21(1)–(3)', 'url' => self::SOURCE.'#page=50'];
|
||||
}
|
||||
|
||||
$labels = array_column($this->questions(), 'label', 'key') + ['currency' => 'Moeda do projecto', 'expected_start_date' => 'Data prevista de início', 'entity_stage' => 'Situação da entidade'];
|
||||
$missingLabels = array_map(fn ($key) => $labels[$key] ?? $key, array_values(array_unique($missing)));
|
||||
|
||||
return ['version' => self::VERSION, 'enabled' => $enabled, 'period_covered' => $period, 'scope_covered' => $covered, 'mode' => $mode, 'reason' => $reasons, 'missing_fields' => array_values(array_unique($missing)), 'missing_labels' => $missingLabels, 'questions' => $this->questions(), 'requirements' => $requirements, 'authority' => $authority, 'authority_source' => $authoritySource, 'next' => $base === false ? 'Rever o âmbito/período; este lote não cobre o pedido' : ($mode === 'indeterminate' ? 'Completar condições do enquadramento' : ($mode === 'authorization' ? 'Preparar estudo e documentos do pedido de autorização' : 'Preparar documentos do pedido de mero registo')), 'calculation_notice' => 'O regime procedimental não altera a taxa de IVA. Reutilize a simulação geral apenas no seu âmbito e período; benefícios, apuramento final e conversão cambial não são presumidos.', 'source' => ['url' => self::SOURCE, 'sha256' => self::HASH], 'notice' => 'Preparação informativa baseada em declarações; documentos associados não constituem validação jurídica, certificado ou submissão à Administração.'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Opportunity;
|
||||
use App\Models\User;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
/** Publication of notices; never receives bids or changes the linked investment. */
|
||||
final class Procurement
|
||||
{
|
||||
public const TEXT = ['title', 'reference', 'responsible_unit', 'modality', 'object', 'object_kind', 'quantities', 'financier', 'instrument', 'edition', 'rule_source', 'financing_agreement', 'eligibility', 'requirements', 'proposal_validity', 'guarantee_status', 'guarantees', 'delivery_channel', 'delivery_address', 'delivery_url', 'public_contact', 'dissemination', 'origin_url', 'origin_published_at', 'clarifications_at', 'deadline', 'opening_at', 'timezone', 'minimum_source', 'minimum_edition', 'minimum_modality', 'minimum_day_basis', 'result', 'internal_notes', 'public_reason', 'minimum_working_days', 'minimum_holidays', 'minimum_calendar_source'];
|
||||
|
||||
public function available(): bool
|
||||
{
|
||||
return config('procurement.enabled') && Schema::hasTable('portal_tenders');
|
||||
}
|
||||
|
||||
public function access(?User $actor): bool
|
||||
{
|
||||
if (! $actor || ! $actor->hasVerifiedEmail()) {
|
||||
return false;
|
||||
}
|
||||
$docs = app(DocumentManagementAccess::class);
|
||||
|
||||
return ! app(PortalProfileAccess::class)->has($actor, 'auditor') && ($docs->full($actor) || $docs->editorial($actor));
|
||||
}
|
||||
|
||||
public function guard(?User $actor): void
|
||||
{
|
||||
abort_unless($this->available(), 404);
|
||||
abort_unless($this->access($actor), 403, 'Use a autorização editorial já atribuída.');
|
||||
}
|
||||
|
||||
public function references(User $actor): array
|
||||
{
|
||||
$this->guard($actor);
|
||||
$editable = app(DocumentManagementAccess::class)->documents($actor)->select('id');
|
||||
|
||||
return ['regimes' => config('procurement.regimes'), 'acts' => config('procurement.acts'),
|
||||
'organizations' => DB::table('organizations')->whereNull('deleted_at')->orderBy('name')->get(['id', 'name']),
|
||||
'sectors' => DB::table('sectors')->orderBy('name')->get(['id', 'name']),
|
||||
'territories' => app(InvestmentGeography::class)->references(true)->orderBy('name')->get(['id', 'name', 'location_type']),
|
||||
'opportunities' => app(PortalProfileAccess::class)->scope(Opportunity::query(), $actor)->orderBy('title')->get(['id', 'title']),
|
||||
'projects' => DB::table('projects')->whereNull('deleted_at')->whereIn('data_classification', ['internal', 'public'])->orderBy('name')->get(['id', 'name']),
|
||||
'documents' => DB::table('portal_resources as r')->join('documents as d', 'd.id', '=', 'r.document_id')->whereNull('d.deleted_at')
|
||||
->where(fn ($q) => $q->whereIn('d.id', $editable)->orWhere(fn ($p) => $p->where('r.published', true)->where('d.data_classification', 'public')))
|
||||
->orderBy('r.title')->get(['r.id', 'r.title', 'r.published', 'd.malware_scan_status'])];
|
||||
}
|
||||
|
||||
public function record(string $id): object
|
||||
{
|
||||
abort_unless($this->available(), 404);
|
||||
|
||||
return DB::table('portal_tenders')->where('id', $id)->firstOrFail();
|
||||
}
|
||||
|
||||
public function save(User $actor, ?string $id, array $input): string
|
||||
{
|
||||
$this->guard($actor);
|
||||
$v = Validator::make($input, ['version' => 'required|integer|min:0', 'reason' => 'required|string|min:20|max:1000', 'draft' => 'required|array']);
|
||||
$v->validate();
|
||||
$draft = $this->normalize($actor, $input['draft']);
|
||||
|
||||
return DB::transaction(function () use ($actor, $id, $input, $draft) {
|
||||
$old = $id ? DB::table('portal_tenders')->where('id', $id)->lockForUpdate()->firstOrFail() : null;
|
||||
abort_unless((int) ($old->version ?? 0) === (int) $input['version'], 409, 'A versão mudou; recupere o concurso antes de guardar.');
|
||||
$id ??= (string) Str::uuid();
|
||||
$version = ($old->version ?? 0) + 1;
|
||||
if ($old) {
|
||||
DB::table('portal_tenders')->where('id', $id)->update(['draft' => json_encode($draft, JSON_THROW_ON_ERROR), 'version' => $version, 'updated_at' => now()]);
|
||||
} else {
|
||||
DB::table('portal_tenders')->insert(['id' => $id, 'draft' => json_encode($draft, JSON_THROW_ON_ERROR), 'version' => $version, 'created_by' => $actor->id, 'created_at' => now(), 'updated_at' => now()]);
|
||||
}
|
||||
$this->version($actor, $id, $version, 'draft', $draft, $input['reason'], false);
|
||||
|
||||
return $id;
|
||||
});
|
||||
}
|
||||
|
||||
private function normalize(User $actor, array $input): array
|
||||
{
|
||||
$rules = array_fill_keys(self::TEXT, 'nullable|string|max:12000');
|
||||
$rules = [...$rules, 'regime' => 'nullable|in:state,external,private,other', 'organization_id' => 'nullable|uuid|exists:organizations,id',
|
||||
'opportunity_id' => 'nullable|integer|exists:opportunities,id', 'project_id' => 'nullable|uuid|exists:projects,id', 'sector_id' => 'nullable|integer|exists:sectors,id',
|
||||
'territory_id' => 'nullable|uuid|exists:geo_locations,id', 'contact_authorized' => 'boolean', 'minimum_days' => 'nullable|integer|min:0|max:3650',
|
||||
'lots' => 'array|max:100', 'lots.*' => 'array:code,description,quantities', 'lots.*.code' => 'required|string|max:80|distinct', 'lots.*.description' => 'required|string|max:3000', 'lots.*.quantities' => 'nullable|string|max:1500',
|
||||
'attachments' => 'array|max:100', 'attachments.*' => 'array:resource_id,public,purpose', 'attachments.*.resource_id' => 'required|integer|exists:portal_resources,id', 'attachments.*.public' => 'required|boolean', 'attachments.*.purpose' => 'required|string|max:150'];
|
||||
foreach (['origin_url', 'delivery_url', 'rule_source'] as $url) {
|
||||
$rules[$url] = 'nullable|url:http,https|max:2000';
|
||||
}
|
||||
$data = Validator::make($input, $rules)->validate();
|
||||
if (! empty($data['opportunity_id'])) {
|
||||
abort_unless(app(PortalProfileAccess::class)->scope(Opportunity::query(), $actor)->whereKey($data['opportunity_id'])->exists(), 403);
|
||||
}
|
||||
if (! empty($data['project_id'])) {
|
||||
abort_unless(DB::table('projects')->where('id', $data['project_id'])->whereNull('deleted_at')->whereIn('data_classification', ['internal', 'public'])->exists(), 422);
|
||||
}
|
||||
foreach ($data['attachments'] ?? [] as $attachment) {
|
||||
$resource = DB::table('portal_resources')->where('id', $attachment['resource_id'])->firstOrFail();
|
||||
abort_unless($this->publicDocuments()->where('r.id', $resource->id)->exists() || app(DocumentManagementAccess::class)->documents($actor)->where('id', $resource->document_id)->exists(), 403);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function preview(User $actor, string $id, string $act): array
|
||||
{
|
||||
$this->guard($actor);
|
||||
$record = $this->record($id);
|
||||
abort_unless(array_key_exists($act, config('procurement.acts')), 422);
|
||||
$p = json_decode($record->draft, true, 512, JSON_THROW_ON_ERROR);
|
||||
Validator::make($p, array_fill_keys(['title', 'reference', 'organization_id', 'modality', 'object', 'regime', 'instrument', 'edition', 'rule_source', 'origin_published_at', 'deadline', 'delivery_channel', 'timezone'], 'required'))->validate();
|
||||
Validator::make($p, ['timezone' => 'timezone', 'object_kind' => 'nullable|in:works,goods,services,mixed,other', 'guarantee_status' => 'nullable|in:required,not_required,unknown'])->validate();
|
||||
Validator::make($p, ['public_reason' => 'required|string|min:20|max:1000'])->validate();
|
||||
$dates = [];
|
||||
foreach (['origin_published_at', 'clarifications_at', 'deadline', 'opening_at'] as $field) {
|
||||
if (empty($p[$field])) {
|
||||
continue;
|
||||
}
|
||||
$validator = Validator::make([$field => $p[$field]], [$field => 'date_format:Y-m-d\TH:i']);
|
||||
$validator->validate();
|
||||
$dates[$field] = CarbonImmutable::createFromFormat('!Y-m-d\TH:i', $p[$field], $p['timezone']);
|
||||
}
|
||||
$this->require($dates['deadline']->greaterThan($dates['origin_published_at']), 'deadline', 'Entrega anterior à publicação de origem.');
|
||||
if (isset($dates['clarifications_at'])) {
|
||||
$this->require($dates['clarifications_at']->betweenIncluded($dates['origin_published_at'], $dates['deadline']), 'clarifications_at', 'Esclarecimentos fora do período de publicação/entrega.');
|
||||
}
|
||||
if (isset($dates['opening_at'])) {
|
||||
$this->require($dates['opening_at']->greaterThanOrEqualTo($dates['deadline']), 'opening_at', 'A abertura indicada é anterior à entrega.');
|
||||
}
|
||||
if (($p['guarantee_status'] ?? '') === 'required') {
|
||||
$this->require(! empty($p['guarantees']), 'guarantees', 'Descreva a garantia documentada exigida.');
|
||||
}
|
||||
if (isset($p['minimum_days'])) {
|
||||
foreach (['minimum_source', 'minimum_edition', 'minimum_modality', 'minimum_day_basis'] as $field) {
|
||||
$this->require(! empty($p[$field]), $field, 'Identifique fonte, edição, modalidade e contagem do prazo mínimo.');
|
||||
}
|
||||
$this->require($p['minimum_modality'] === $p['modality'], 'minimum_modality', 'A regra indicada pertence a outra modalidade.');
|
||||
$earliest = $dates['origin_published_at'];
|
||||
if ($p['minimum_day_basis'] === 'business') {
|
||||
$this->require(! empty($p['minimum_calendar_source']), 'minimum_calendar_source', 'Identifique a fonte do calendário de dias úteis e feriados.');
|
||||
$weekdays = array_filter(array_map('trim', explode(',', (string) ($p['minimum_working_days'] ?? ''))));
|
||||
$this->require(count($weekdays) > 0 && ! array_diff($weekdays, ['1', '2', '3', '4', '5', '6', '7']), 'minimum_working_days', 'Indique os dias de trabalho documentados (1=segunda, 7=domingo).');
|
||||
$holidays = array_values(array_filter(preg_split('/[\s,;]+/', (string) ($p['minimum_holidays'] ?? ''))));
|
||||
Validator::make(['dates' => $holidays], ['dates' => 'array|max:1000', 'dates.*' => 'date_format:Y-m-d'])->validate();
|
||||
for ($left = (int) $p['minimum_days'],$iterations = 0; $left > 0; $iterations++) {
|
||||
$this->require($iterations < 36500, 'minimum_days', 'Calendário insuficiente para calcular o prazo indicado.');
|
||||
$earliest = $earliest->addDay();
|
||||
if (in_array((string) $earliest->isoWeekday(), $weekdays, true) && ! in_array($earliest->format('Y-m-d'), $holidays, true)) {
|
||||
$left--;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->require($p['minimum_day_basis'] === 'calendar', 'minimum_day_basis', 'Identifique dias corridos ou calendário de dias úteis documentado.');
|
||||
$earliest = $earliest->addDays((int) $p['minimum_days']);
|
||||
}
|
||||
$this->require($dates['deadline']->greaterThanOrEqualTo($earliest), 'deadline', 'Prazo inferior ao mínimo documentado para esta modalidade/edição/calendário.');
|
||||
}
|
||||
$previous = $record->published_version_id ? $this->publishedVersion($record->published_version_id) : null;
|
||||
$this->require($previous ? $act !== 'initial' : $act === 'initial', 'act', $previous ? 'O anúncio inicial já existe; publique um acto associado.' : 'Publique primeiro o anúncio inicial.');
|
||||
if ($previous) {
|
||||
$before = $previous['payload'];
|
||||
if (($before['deadline'] ?? '') !== $p['deadline'] || ($before['timezone'] ?? '') !== $p['timezone']) {
|
||||
$this->require($act === 'extension', 'act', 'Uma mudança do prazo exige acto de prorrogação, com data anterior e nova.');
|
||||
}
|
||||
if ($act === 'extension') {
|
||||
$this->require($dates['deadline']->greaterThan(CarbonImmutable::parse($before['deadline'], $before['timezone'])), 'deadline', 'A prorrogação deve avançar o prazo anterior.');
|
||||
$p['deadline_previous'] = $before['deadline'];
|
||||
$p['timezone_previous'] = $before['timezone'];
|
||||
}
|
||||
$p['extended'] = ($before['extended'] ?? false) || $act === 'extension';
|
||||
$p['procedural_state'] = $before['procedural_state'] ?? null;
|
||||
}
|
||||
if ($act === 'award') {
|
||||
$p['procedural_state'] = 'results';
|
||||
}
|
||||
if ($act === 'cancellation') {
|
||||
$p['procedural_state'] = 'cancelled';
|
||||
}
|
||||
if (in_array($act, ['award', 'cancellation', 'other'], true)) {
|
||||
$this->require(! empty($p['result']), 'result', 'Descreva o acto ou resultado documentado.');
|
||||
}
|
||||
$p['organization_name'] = DB::table('organizations')->where('id', $p['organization_id'])->whereNull('deleted_at')->value('name');
|
||||
$this->require((bool) $p['organization_name'], 'organization_id', 'Entidade eliminada; recupere a referência.');
|
||||
$p['sector_name'] = empty($p['sector_id']) ? null : DB::table('sectors')->where('id', $p['sector_id'])->value('name');
|
||||
$p['territory_name'] = empty($p['territory_id']) ? null : app(InvestmentGeography::class)->references(true)->where('id', $p['territory_id'])->value('name');
|
||||
if (! empty($p['territory_id'])) {
|
||||
$this->require((bool) $p['territory_name'], 'territory_id', 'Referência territorial indisponível.');
|
||||
}
|
||||
unset($p['internal_notes']);
|
||||
if (! ($p['contact_authorized'] ?? false)) {
|
||||
unset($p['public_contact']);
|
||||
}
|
||||
$attachments = [];
|
||||
foreach ($p['attachments'] ?? [] as $item) {
|
||||
if (! $item['public']) {
|
||||
continue;
|
||||
}
|
||||
$doc = $this->publicDocuments()->where('r.id', $item['resource_id'])->first(['r.id', 'r.title', 'd.sha256', 'd.id as document_id']);
|
||||
$this->require((bool) $doc, 'attachments', 'Anexo público exige publicação autorizada, scan limpo e direitos sem restrição na biblioteca.');
|
||||
$attachments[] = [...$item, 'title' => $doc->title, 'sha256' => trim($doc->sha256), 'document_id' => $doc->document_id];
|
||||
}
|
||||
$p['attachments'] = $attachments;
|
||||
if (! empty($p['opportunity_id'])) {
|
||||
$o = Opportunity::published()->whereKey($p['opportunity_id'])->first(['id', 'title', 'slug']);
|
||||
$p['opportunity'] = $o ? ['title' => $o->title, 'url' => '/oportunidades/'.$o->slug] : null;
|
||||
}
|
||||
if (! empty($p['project_id'])) {
|
||||
$p['project_name'] = DB::table('projects')->where('id', $p['project_id'])->whereNull('deleted_at')->where('data_classification', 'public')->value('name');
|
||||
}
|
||||
unset($p['opportunity_id'],$p['project_id']);
|
||||
$plan = ['id' => $id, 'version' => $record->version, 'act' => $act, 'payload' => $p];
|
||||
$plan['hash'] = hash_hmac('sha256', json_encode($plan, JSON_THROW_ON_ERROR), config('app.key'));
|
||||
|
||||
return $plan;
|
||||
}
|
||||
|
||||
public function publish(User $actor, string $id, array $input): void
|
||||
{
|
||||
$this->guard($actor);
|
||||
Validator::make($input, ['hash' => 'required|string|size:64', 'act' => 'required|string', 'reason' => 'required|string|min:20|max:1000'])->validate();
|
||||
DB::transaction(function () use ($actor, $id, $input) {
|
||||
$record = DB::table('portal_tenders')->where('id', $id)->lockForUpdate()->firstOrFail();
|
||||
$plan = $this->preview($actor, $id, $input['act']);
|
||||
abort_unless(hash_equals($plan['hash'], $input['hash']), 409, 'Prévia alterada; reveja o conteúdo antes de publicar.');
|
||||
abort_unless($input['reason'] === $plan['payload']['public_reason'], 409, 'O motivo público deve coincidir com a prévia.');
|
||||
$version = $record->version + 1;
|
||||
$vid = $this->version($actor, $id, $version, $input['act'], $plan['payload'], $input['reason'], true);
|
||||
DB::table('portal_tenders')->where('id', $id)->update(['published_version_id' => $vid, 'version' => $version, 'updated_at' => now()]);
|
||||
});
|
||||
}
|
||||
|
||||
private function version(User $actor, string $id, int $version, string $act, array $payload, string $reason, bool $published): string
|
||||
{
|
||||
$vid = (string) Str::uuid();
|
||||
DB::table('portal_tender_versions')->insert(['id' => $vid, 'tender_id' => $id, 'version' => $version, 'act' => $act, 'payload' => json_encode($payload, JSON_THROW_ON_ERROR), 'reason' => $reason, 'actor_id' => $actor->id, 'published_at' => $published ? now() : null, 'created_at' => now()]);
|
||||
DB::table('audit_events')->insert(['source_system' => 'procurement', 'entity_type' => 'portal_tenders', 'entity_id' => $id, 'actor_user_id' => $actor->id, 'action' => $published ? 'notice_published' : 'tender_draft_saved', 'data_classification' => 'internal', 'after_data' => json_encode(['version_id' => $vid, 'act' => $act, 'reason' => $reason], JSON_THROW_ON_ERROR)]);
|
||||
|
||||
return $vid;
|
||||
}
|
||||
|
||||
public function publicDocuments(): Builder
|
||||
{
|
||||
return DB::table('portal_resources as r')->join('documents as d', 'd.id', '=', 'r.document_id')->where('r.published', true)->where('r.rights_status', '!=', 'restricted')->where('d.rights_status', '!=', 'restricted')->where('d.data_classification', 'public')->where('d.status', 'active')->whereNull('d.deleted_at')->where('d.malware_scan_status', 'clean')->whereColumn('d.sha256', 'd.malware_scanned_sha256')
|
||||
->where(fn ($q) => $q->whereNull('d.source_system')->orWhereNotIn('d.source_system', ['opportunity-resources', 'opportunity-image-derivatives']))
|
||||
->whereNotExists(fn ($q) => $q->selectRaw('1')->from('opportunity_documents as od')->whereColumn('od.document_id', 'd.id')->whereRaw("NOT jsonb_exists(COALESCE(od.payload,'{}'::jsonb),'library_source')"))
|
||||
->whereNotExists(fn ($q) => $q->selectRaw('1')->from('opportunity_images as oi')->whereColumn('oi.document_id', 'd.id')->whereRaw("NOT jsonb_exists(COALESCE(oi.payload,'{}'::jsonb),'library_source')"));
|
||||
}
|
||||
|
||||
public function publishedVersion(string $id): array
|
||||
{
|
||||
$v = DB::table('portal_tender_versions')->where('id', $id)->whereNotNull('published_at')->firstOrFail();
|
||||
$p = json_decode($v->payload, true, 512, JSON_THROW_ON_ERROR);
|
||||
foreach ($p['attachments'] ?? [] as $i => $item) {
|
||||
$p['attachments'][$i]['available'] = $this->publicDocuments()->where('r.id', $item['resource_id'])->where('d.id', $item['document_id'])->where('d.sha256', $item['sha256'])->exists();
|
||||
}
|
||||
|
||||
return ['id' => $v->id, 'version' => $v->version, 'act' => $v->act, 'payload' => $p, 'published_at' => $v->published_at, 'reason' => $v->reason];
|
||||
}
|
||||
|
||||
public function state(array $version): string
|
||||
{
|
||||
$p = $version['payload'];
|
||||
if (in_array($p['procedural_state'] ?? '', ['cancelled', 'results'], true)) {
|
||||
return $p['procedural_state'];
|
||||
}
|
||||
if (CarbonImmutable::parse($p['deadline'], $p['timezone'])->isPast()) {
|
||||
return 'deadline_ended';
|
||||
}
|
||||
|
||||
return ($p['extended'] ?? false) ? 'extended' : 'open';
|
||||
}
|
||||
|
||||
private function require(bool $condition, string $field, string $message): void
|
||||
{
|
||||
if (! $condition) {
|
||||
throw ValidationException::withMessages([$field => $message]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace App\Services;
|
||||
|
||||
use App\Models\InvestmentLegalSource;
|
||||
use App\Models\SimulationTaxRuleVersion;
|
||||
use Brick\Math\BigRational;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
@@ -35,8 +36,11 @@ final class SimulationPublishedFiscal
|
||||
->where('scope_key', 'mz.internal-general-net.mzn');
|
||||
}
|
||||
|
||||
public function admit(array $input): array
|
||||
public function admit(array $input, bool $missingBase = false): array
|
||||
{
|
||||
if (isset($input['monthly_sales'])) {
|
||||
return $this->admitMonthly($input);
|
||||
}
|
||||
// Called inside createPayload, after idempotent replay, never by the worker.
|
||||
abort_unless(DB::transactionLevel() > 0, 500);
|
||||
abort_unless(config('simulation-fiscal-consumer.enabled'), 503,
|
||||
@@ -63,6 +67,9 @@ final class SimulationPublishedFiscal
|
||||
$input['reference_date'], $input['reference_date'], ['currency' => $input['currency'], 'operation' => $input['operation']]);
|
||||
$rate = collect($resolution)->firstWhere('parameter_code', 'IVA_GENERAL_RATE');
|
||||
$missing = [];
|
||||
if ($missingBase) {
|
||||
$missing[] = 'taxable_sales';
|
||||
}
|
||||
$evaluation = null;
|
||||
if (! ($input['scope_declared'] ?? false)) {
|
||||
$missing[] = 'operation_scope';
|
||||
@@ -79,4 +86,51 @@ final class SimulationPublishedFiscal
|
||||
|
||||
return app(SimulationFiscalSnapshot::class)->build($version, $input, $resolution, $evaluation, $missing, true);
|
||||
}
|
||||
|
||||
private function admitMonthly(array $input): array
|
||||
{
|
||||
$rows = [];
|
||||
$baseTotal = BigRational::zero();
|
||||
$vatTotal = BigRational::zero();
|
||||
$complete = true;
|
||||
$previous = null;
|
||||
foreach ($input['monthly_sales'] as $sale) {
|
||||
abort_unless($previous === null || $sale['date'] === date('Y-m-d', strtotime($previous.' +1 month')), 422, 'A projecção deve conter meses consecutivos, sem duplicações.');
|
||||
$previous = $sale['date'];
|
||||
$known = $sale['amount'] !== null && $sale['amount'] !== '';
|
||||
$net = $input['sales_vat_basis'] === 'net';
|
||||
$month = $this->admit(['base' => $known && $net ? $sale['amount'] : null,
|
||||
'currency' => $input['currency'], 'reference_date' => $sale['date'],
|
||||
'operation' => 'internal_general_net', 'scope_declared' => $input['scope_declared']], ! $known || ! $net);
|
||||
$snapshot ??= $month;
|
||||
$rate = collect($month['fiscal_provenance']['resolution'])->firstWhere('parameter_code', 'IVA_GENERAL_RATE');
|
||||
$vat = $month['result']['value'];
|
||||
$status = ! $known ? 'missing_sales' : (! $net ? 'unsupported_basis' : ($vat === null ? 'indeterminate' : 'calculated'));
|
||||
$rows[] = ['date' => $sale['date'], 'sales_declared' => $sale['amount'],
|
||||
'net_sales' => $known && $net ? $sale['amount'] : null,
|
||||
'rate' => $vat !== null && ($rate['status'] ?? null) === 'resolved' ? $rate['value'] : null,
|
||||
'vat' => $vat, 'gross_sales' => $vat === null ? null : (string) BigRational::of($sale['amount'])->plus($vat)->toBigDecimal(),
|
||||
'status' => $status, 'rule_id' => $month['fiscal_provenance']['rule']['id'],
|
||||
'rule_version' => $month['fiscal_provenance']['rule']['rule_version']];
|
||||
if ($vat === null) {
|
||||
$complete = false;
|
||||
} else {
|
||||
$baseTotal = $baseTotal->plus($sale['amount']);
|
||||
$vatTotal = $vatTotal->plus($vat);
|
||||
}
|
||||
}
|
||||
$value = $complete ? (string) $vatTotal->toBigDecimal() : null;
|
||||
$snapshot['base'] = $complete ? (string) $baseTotal->toBigDecimal() : null;
|
||||
$snapshot['indicative_total'] = $value;
|
||||
$snapshot['result']['value'] = $value;
|
||||
$snapshot['result']['status'] = $complete ? 'calculated' : 'indeterminate';
|
||||
$snapshot['result']['missing_inputs'] = $complete ? [] : ['monthly_sales_or_net_basis_or_scope'];
|
||||
$snapshot['result']['explanation'] = 'IVA mensal sobre vendas declaradas sem IVA, pela versão publicada congelada na admissão. Valores com IVA incluído ou sem informação permanecem indeterminados. Não é imposto final a pagar.';
|
||||
$snapshot['presentation']['value'] = $value;
|
||||
$snapshot['intermediate'] = null;
|
||||
$snapshot['monthly_projection'] = ['input' => $input, 'rows' => $rows,
|
||||
'cash_treatment' => 'Informação separada da tesouraria: os recebimentos declarados não são aumentados pelo IVA nem o IVA é deduzido como custo ou imposto a pagar. Calendário de cobrança e liquidação fiscal não é inferido.'];
|
||||
|
||||
return $snapshot;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ class TerritoryGeometryImport
|
||||
|
||||
private function guard(User $actor, bool $write = false): void
|
||||
{
|
||||
abort_unless($actor->can('manage-portal'), 403);
|
||||
abort_unless($actor->can('manage-portal') || app(ScopedPortalAdministration::class)->administrative($actor), 403);
|
||||
abort_unless(config('geometry-import.enabled'), 503, 'Importação cartográfica não disponibilizada.');
|
||||
if (! $write) {
|
||||
return;
|
||||
|
||||
@@ -250,7 +250,8 @@ class VirtualDesk
|
||||
* @return array<string,mixed> */
|
||||
public function validateFields(array $payload, array $definition, bool $submitting): array
|
||||
{
|
||||
$rules = ['payload' => ['required', 'array:'.implode(',', [...array_keys($definition['fields']), 'country', 'declared_origin_country_code'])],
|
||||
$rules = ['payload' => ['required', 'array:'.implode(',', [...array_keys($definition['fields']), 'country', 'declared_origin_country_code', 'context_reuse'])],
|
||||
'payload.context_reuse' => ['nullable', 'string', 'max:10000'],
|
||||
'payload.country' => ['nullable', 'string', 'max:100', app(CountryCatalogue::class)->legacyCountryRule()],
|
||||
'payload.declared_origin_country_code' => ['nullable', 'string', app(CountryCatalogue::class)->rule()]];
|
||||
foreach ($definition['fields'] as $key => $field) {
|
||||
@@ -388,7 +389,7 @@ class VirtualDesk
|
||||
abort_if($this->owns($actor, $case, false), 403, 'O proponente não pode analisar ou decidir o seu próprio pedido.');
|
||||
abort_unless($actor->can('manage-portal') || (int) $request->analyst_id === $actor->id, 403);
|
||||
if (in_array($data['status'], ['accepted', 'rejected', 'closed'], true)) {
|
||||
abort_unless(app(ScopedPortalAdministration::class)->deskDecision($actor,$request), 403);
|
||||
abort_unless(app(ScopedPortalAdministration::class)->deskDecision($actor, $request), 403);
|
||||
}
|
||||
abort_unless(in_array($data['status'], config('virtual-desk.transitions.'.$request->status, []), true), 409, 'Transição inválida.');
|
||||
}
|
||||
@@ -412,13 +413,13 @@ class VirtualDesk
|
||||
|
||||
return DB::transaction(function () use ($actor, $id, $data) {
|
||||
$case = $this->case($actor, $id, false, true);
|
||||
abort_unless($this->owns($actor, $case) || app(ScopedPortalAdministration::class)->deskCaseDecision($actor,$case->id), 403);
|
||||
abort_unless($this->owns($actor, $case) || app(ScopedPortalAdministration::class)->deskCaseDecision($actor, $case->id), 403);
|
||||
abort_unless((int) $case->version === (int) $data['version'], 409);
|
||||
$workflow = json_decode($case->workflow, true, 512, JSON_THROW_ON_ERROR);
|
||||
$current = $workflow[$data['stage']];
|
||||
$decision = in_array($data['status'], ['completed', 'not_applicable'], true);
|
||||
if ($decision) {
|
||||
abort_unless(app(ScopedPortalAdministration::class)->deskCaseDecision($actor,$case->id) && ! $this->owns($actor, $case, false), 403, 'Conclusão documental exige revisão interna por outra pessoa autorizada.');
|
||||
abort_unless(app(ScopedPortalAdministration::class)->deskCaseDecision($actor, $case->id) && ! $this->owns($actor, $case, false), 403, 'Conclusão documental exige revisão interna por outra pessoa autorizada.');
|
||||
abort_unless(trim($data['evidence'] ?? '') !== '', 422, 'Identifique a evidência documental.');
|
||||
foreach ($current['dependencies'] as $dependency) {
|
||||
abort_unless(in_array($workflow[$dependency]['status'], ['completed', 'not_applicable'], true) || (($data['exception'] ?? false) && ($data['document_id'] ?? null)), 422, 'Dependência '.$dependency.' pendente; uma exceção exige evidência própria.');
|
||||
|
||||
@@ -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']],
|
||||
];
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
return [
|
||||
// Explicit opt-in after authenticated installation; never infer activation from schema/ready.
|
||||
'private_preparation' => env('PRIVATE_INVESTMENT_PREPARATION_ENABLED', false),
|
||||
'enabled' => env('INVESTOR_PATH_ENABLED', false),
|
||||
'definition_id' => '61000919-0000-4000-8000-000000000001',
|
||||
];
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
[
|
||||
{
|
||||
"external_id": "legacy-tempo-post-62413-20170626",
|
||||
"sha256": "b471c1afd7235f359acdcc16a93088f77e1f988706c8d3c4d4f11c5d560aea47",
|
||||
"kind": "historico_noticioso",
|
||||
"original_image_sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-73945-20170731",
|
||||
"sha256": "2aa0ce0eb305bfdf93e390a978d3694d0fbc0129034077b68cebce418f878415",
|
||||
"kind": "historico_noticioso",
|
||||
"original_image_sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-76474-20170808",
|
||||
"sha256": "8f769aec4715449d056c8c25294060b2ff290ec0893c28e9bdcc4e8092fc75f5",
|
||||
"kind": "historico_noticioso",
|
||||
"original_image_sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-62414-20170626",
|
||||
"sha256": "a040c0b7cb95dd7f053f4d13662dd58b2b06453b6fa61a2d531d0257a8db8c17",
|
||||
"kind": "historico_noticioso",
|
||||
"original_image_sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-62418-20170626",
|
||||
"sha256": "e8ccafa68f0d1aa80f2e41d9e09f7b13e7c653a8472a731798cfd5e04ce2963e",
|
||||
"kind": "historico_noticioso",
|
||||
"original_image_sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-75002-20170804",
|
||||
"sha256": "7b6fbd998be2b47b96e9a4724560c192c502709fa308d6590c0797ddcdb10544",
|
||||
"kind": "historico_noticioso",
|
||||
"original_image_sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-73246-20170728",
|
||||
"sha256": "430ee8674c71104fc126bed2911d676f150ac623857a1ada75662987fd9ac390",
|
||||
"kind": "historico_noticioso",
|
||||
"original_image_sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-77232-20170811",
|
||||
"sha256": "27544d3127803b7495bef9fd633147d0225d8c31816c4d59ed05a2edd59de070",
|
||||
"kind": "historico_noticioso",
|
||||
"original_image_sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-62416-20170626",
|
||||
"sha256": "ac575ce340571192552c89b63f0bd62a84dbc47828e6930133af5ff70e249319",
|
||||
"kind": "historico_noticioso",
|
||||
"original_image_sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-62929-20170627",
|
||||
"sha256": "488017b062c6760659d9147b7f12aea4afd8d824121adb9462e2c7a2ae58437b",
|
||||
"kind": "historico_noticioso",
|
||||
"original_image_sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-62649-20170626",
|
||||
"sha256": "38a858713472759a2d20a308bdd9d8afa9cfb76dcaf8d11ca709e89261573e42",
|
||||
"kind": "historico_noticioso",
|
||||
"original_image_sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-62739-20170626",
|
||||
"sha256": "5870fc0fad9bb7af537b7b1468151cde138b1b886ce846c7be25647b630245dd",
|
||||
"kind": "historico_noticioso",
|
||||
"original_image_sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-62854-20170627",
|
||||
"sha256": "44b1ba9d593bb625cf53de8909e78eb0b3129742c3ec9d58ec68c984e8930bce",
|
||||
"kind": "historico_noticioso",
|
||||
"original_image_sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-63128-20170627",
|
||||
"sha256": "74be8f29e5053f53ab1a39a2f63208145c30f23357ca6d78b637dd02bf9c1fb4",
|
||||
"kind": "historico_noticioso",
|
||||
"original_image_sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-63181-20170627",
|
||||
"sha256": "3d0f9ac14d36156d8178e33f8616d6406f31ea4cd69c8308cbf4227586d7ccb3",
|
||||
"kind": "historico_noticioso",
|
||||
"original_image_sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-63146-20170627",
|
||||
"sha256": "542353466cc6d82b651619c495f138b0576a4be41d21aad194ae2005050cb1bc",
|
||||
"kind": "historico_noticioso",
|
||||
"original_image_sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-62688-20170626",
|
||||
"sha256": "a210a789b06d425822f4124f74226a2b27246c086c5ec5f041f1b2237d7c2b65",
|
||||
"kind": "historico_noticioso",
|
||||
"original_image_sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-63248-20170628",
|
||||
"sha256": "6c30e61197b8568332af6d9b19bbba7ad8665b3873bb031a8c8aedd41536b060",
|
||||
"kind": "historico_noticioso",
|
||||
"original_image_sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-173220-20260616",
|
||||
"sha256": "5b76c3d84dcf7f9a489686d90e3857ae6536705c918e841310bb42b354e80976",
|
||||
"kind": "setorial",
|
||||
"original_image_sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-173222-20260616",
|
||||
"sha256": "e64d7063d1ac4dda028c97e1ba64938d2a9e426a0b630c57ee5df52a41312cc7",
|
||||
"kind": "setorial",
|
||||
"original_image_sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-173482-20260915",
|
||||
"sha256": "6f888e133a779c719073246f89bcae150198e5897c0e7d9b7ba75f54c54a7830",
|
||||
"kind": "trabalho_domestico",
|
||||
"original_image_sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-173484-20260915",
|
||||
"sha256": "297df455a9c6fd1f0ab7ca7bbe124ce483c86fe55be26baa695f03089b3ac049",
|
||||
"kind": "trabalho_domestico",
|
||||
"original_image_sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-173478-20260915",
|
||||
"sha256": "d4e320ed99dd9d53d205e47932f73651976d43081d5d39f2bfc2996901408644",
|
||||
"kind": "trabalho_domestico",
|
||||
"original_image_sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-173480-20260915",
|
||||
"sha256": "c3ee6401eda5b582c61969b5e3b74fdf26ef34ca481b1aca8c4c309d845147ae",
|
||||
"kind": "trabalho_domestico",
|
||||
"original_image_sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-173476-20260915",
|
||||
"sha256": "72fbf28391db4804e0637b64e7f59e9fa597812a2681b4562dc3d6dfb4093a30",
|
||||
"kind": "trabalho_domestico",
|
||||
"original_image_sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-173470-20260915",
|
||||
"sha256": "26feeae6b8d04972c7a64ee360583cccdcaeb4b0d8795fd7bc2fd3c38866cc19",
|
||||
"kind": "trabalho_domestico",
|
||||
"original_image_sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-173474-20260915",
|
||||
"sha256": "9fa741a17b746bfa2f3ce7b6ce1d2bb34d3a35d3b58330b0554f9e0a6844f049",
|
||||
"kind": "trabalho_domestico",
|
||||
"original_image_sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-173463-20260915",
|
||||
"sha256": "4f34b7bac4b3030c0614844d379f6e3cfad1b7f6ea3db575a58c33ae033b0e73",
|
||||
"kind": "trabalho_domestico",
|
||||
"original_image_sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"
|
||||
}
|
||||
]
|
||||
+1
-1
@@ -23,7 +23,7 @@ return [
|
||||
'opportunity_other_qa_subtypes' => [],
|
||||
'operator_name' => env('PORTAL_OPERATOR_NAME'),
|
||||
// Tempo, 19/10/2026: institutional contact; commercial department remains separate.
|
||||
'institutional_contact_email' => env('PORTAL_INSTITUTIONAL_CONTACT_EMAIL', 'info@tempo.co.mz'),
|
||||
'institutional_contact_email' => env('PORTAL_INSTITUTIONAL_CONTACT_EMAIL', 'comercial@tempo.co.mz'),
|
||||
'support' => [
|
||||
'email' => env('PORTAL_SUPPORT_EMAIL'),
|
||||
'phone' => env('PORTAL_SUPPORT_PHONE'),
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'enabled' => (bool) env('PROCUREMENT_ENABLED', false),
|
||||
'regimes' => ['state' => 'Contratação pública do Estado', 'external' => 'Financiamento externo', 'private' => 'Concurso privado', 'other' => 'Outro regime documentado'],
|
||||
'acts' => ['initial' => 'Anúncio inicial', 'clarification' => 'Esclarecimento', 'correction' => 'Rectificação', 'extension' => 'Prorrogação', 'award' => 'Resultado / adjudicação', 'cancellation' => 'Cancelamento', 'other' => 'Outro acto'],
|
||||
];
|
||||
@@ -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"
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('portal_tenders', function (Blueprint $t) {
|
||||
$t->uuid('id')->primary();
|
||||
$t->jsonb('draft');
|
||||
$t->unsignedInteger('version')->default(1);
|
||||
$t->uuid('published_version_id')->nullable();
|
||||
$t->foreignId('created_by')->constrained('users')->restrictOnDelete();
|
||||
$t->timestampsTz();
|
||||
});
|
||||
Schema::create('portal_tender_versions', function (Blueprint $t) {
|
||||
$t->uuid('id')->primary();
|
||||
$t->foreignUuid('tender_id')->constrained('portal_tenders')->restrictOnDelete();
|
||||
$t->unsignedInteger('version');
|
||||
$t->string('act', 24);
|
||||
$t->jsonb('payload');
|
||||
$t->text('reason');
|
||||
$t->foreignId('actor_id')->constrained('users')->restrictOnDelete();
|
||||
$t->timestampTz('published_at')->nullable();
|
||||
$t->timestampTz('created_at')->useCurrent();
|
||||
$t->unique(['tender_id', 'version']);
|
||||
});
|
||||
Schema::create('portal_tender_watches', function (Blueprint $t) {
|
||||
$t->foreignUuid('tender_id')->constrained('portal_tenders')->restrictOnDelete();
|
||||
$t->foreignId('user_id')->constrained('users')->restrictOnDelete();
|
||||
$t->uuid('seen_version_id')->nullable();
|
||||
$t->timestampsTz();
|
||||
$t->primary(['tender_id', 'user_id']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
if (DB::table('portal_tenders')->exists()) {
|
||||
throw new RuntimeException('Preserve procedures, acts and history; use a reviewed data reversal.');
|
||||
}
|
||||
Schema::dropIfExists('portal_tender_watches');
|
||||
Schema::dropIfExists('portal_tender_versions');
|
||||
Schema::dropIfExists('portal_tenders');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
FROM node:22-bookworm AS node
|
||||
|
||||
FROM php:8.4-cli-bookworm
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends libpq-dev libzip-dev unzip git curl \
|
||||
&& docker-php-ext-install -j"$(nproc)" pdo_pgsql pgsql zip \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY --from=node /usr/local/bin/node /usr/local/bin/node
|
||||
COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules
|
||||
RUN ln -s ../lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm \
|
||||
&& ln -s ../lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx \
|
||||
&& ln -s ../lib/node_modules/corepack/dist/corepack.js /usr/local/bin/corepack
|
||||
|
||||
WORKDIR /app
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci --no-audit --no-fund
|
||||
COPY . .
|
||||
@@ -0,0 +1,13 @@
|
||||
FROM php:8.4-cli-bookworm
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends libpq-dev libzip-dev unzip git curl \
|
||||
&& docker-php-ext-install -j"$(nproc)" pdo_pgsql pgsql zip \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
|
||||
WORKDIR /var/www/html
|
||||
COPY composer.json composer.lock ./
|
||||
RUN composer install --no-interaction --prefer-dist --no-progress --no-scripts
|
||||
COPY . .
|
||||
RUN composer dump-autoload --optimize
|
||||
@@ -0,0 +1,176 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>InvestMoz — Levantamento funcional e modelos</title>
|
||||
<style>
|
||||
@page { size: A4; margin: 18mm 17mm 18mm 17mm; }
|
||||
body { font-family: Aptos, Calibri, Arial, sans-serif; color:#1d2939; font-size:10.5pt; line-height:1.42; }
|
||||
h1 { color:#123d68; font-size:26pt; margin:0 0 8pt; }
|
||||
h2 { color:#123d68; font-size:17pt; border-bottom:2px solid #13a789; padding-bottom:5pt; margin-top:24pt; }
|
||||
h3 { color:#145b82; font-size:13pt; margin:17pt 0 5pt; }
|
||||
p { margin:5pt 0 8pt; }
|
||||
.cover { padding-top:100pt; page-break-after:always; }
|
||||
.kicker { text-transform:uppercase; letter-spacing:2px; color:#138a77; font-weight:bold; }
|
||||
.subtitle { color:#52657a; font-size:15pt; }
|
||||
.meta { margin-top:35pt; padding:12pt; background:#eff6fb; border-left:4px solid #1d75bd; }
|
||||
.callout { background:#fff7e8; border-left:4px solid #ee9b20; padding:9pt 12pt; margin:12pt 0; }
|
||||
.note { color:#52657a; font-size:9pt; }
|
||||
table { border-collapse:collapse; width:100%; margin:8pt 0 14pt; font-size:9.2pt; }
|
||||
th { background:#123d68; color:white; text-align:left; padding:6pt; }
|
||||
td { border:1px solid #cbd5e1; padding:5pt 6pt; vertical-align:top; }
|
||||
tr:nth-child(even) td { background:#f5f8fb; }
|
||||
.diagram { text-align:center; margin:12pt 0; page-break-inside:avoid; }
|
||||
.diagram svg { width:100%; height:auto; }
|
||||
.legend { color:#52657a; font-size:9pt; }
|
||||
.small { font-size:9pt; }
|
||||
.pagebreak { page-break-before:always; }
|
||||
ul { margin-top:3pt; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<section class="cover">
|
||||
<p class="kicker">Documento de análise funcional</p>
|
||||
<h1>InvestMoz</h1>
|
||||
<p class="subtitle">Levantamento profissional do projecto, casos de uso reais, actores e diagramas</p>
|
||||
<div class="meta"><b>Data:</b> 9 de Outubro de 2026<br><b>Âmbito:</b> aplicação Laravel/React e documentação do repositório Investidor-V2<br><b>Versão:</b> 1.0 · análise do código e dos documentos disponíveis</div>
|
||||
<p style="margin-top:42pt;font-size:13pt">Portal de descoberta de oportunidades e serviços de investimento, ligado a fluxos autenticados de candidatura, tratamento institucional, evidência documental e acompanhamento.</p>
|
||||
<p class="note" style="margin-top:55pt">Documento técnico-funcional. A presença de uma rota, tabela, job ou descrição não demonstra, por si só, activação em produção.</p>
|
||||
</section>
|
||||
|
||||
<h2>1. Sumário executivo</h2>
|
||||
<p>O InvestMoz é uma plataforma digital que aproxima investidores, promotores e instituições. A experiência começa no portal público, onde se consultam sectores, oportunidades, negócios existentes, território, zonas e serviços. A partir daí, a pessoa pode manifestar interesse, iniciar uma simulação ou pedir apoio. Os percursos que exigem continuidade passam para a área autenticada, onde se mantêm perfil, rascunhos, candidaturas, anexos e comunicação.</p>
|
||||
<p>Do lado institucional, os pedidos podem ser revistos e acompanhados por CRM local, tarefas, revisões e eventos. A plataforma também contém domínios de catálogo territorial, zonas de investimento, documentação, governação fiscal, educação e competências, assistência de terra, parcerias, balcão virtual e IA baseada em fontes.</p>
|
||||
<div class="callout"><b>Leitura funcional:</b> trata-se de uma plataforma de encaminhamento e gestão de jornadas de investimento, e não apenas de um catálogo de anúncios. A camada pública informa e capta pedidos; a camada autenticada conserva o trabalho; a camada de gestão analisa, valida e decide segundo permissões e evidência.</div>
|
||||
<p>O README descreve a base inicial de sectores, oportunidades e manifestações de interesse. Rotas, controladores, migrations e documentação de domínio mostram uma solução mais abrangente. O levantamento de casos detalhados tem 24 casos de uso; este relatório acrescenta cenários concretos, diagramas UML/ER e a leitura da frequência dos eventos.</p>
|
||||
|
||||
<h2>2. O que o projecto faz</h2>
|
||||
<table><tr><th>Capacidade</th><th>Valor para o utilizador</th><th>Exemplos de dados</th></tr>
|
||||
<tr><td>Descoberta pública</td><td>Encontrar oportunidades, negócios, sectores, serviços e referências territoriais.</td><td>sectors, opportunities, existing_businesses, investment_zones</td></tr>
|
||||
<tr><td>Captação de interesse</td><td>Enviar manifestação associada a uma oportunidade ou negócio, com consentimento.</td><td>interest_requests, consents, receipts</td></tr>
|
||||
<tr><td>Jornada do investidor</td><td>Guardar itens, criar dossier/submissão, anexar documentos e acompanhar mensagens.</td><td>investment_submissions, revisions, messages, attachments</td></tr>
|
||||
<tr><td>Tratamento institucional</td><td>Qualificar pedidos, atribuir responsável, criar tarefas e acompanhar a negociação.</td><td>leads, contacts, crm_opportunities, tasks, activities</td></tr>
|
||||
<tr><td>Planeamento territorial</td><td>Relacionar projectos, clusters, corredores, localizações e zonas.</td><td>projects, economic_clusters, development_corridors, geo_locations</td></tr>
|
||||
<tr><td>Documentos e evidência</td><td>Preservar origem, versões, hash, scan, classificação e autorização de divulgação.</td><td>documents, document_versions, malware_scans, associations</td></tr>
|
||||
<tr><td>Simulação e regras fiscais</td><td>Guardar rascunhos, executar simulações com parâmetros/fontes rastreáveis e governar versões.</td><td>simulations, scenarios, results, tax_rule_versions, legal_sources</td></tr>
|
||||
<tr><td>Serviços complementares</td><td>Registar educação, assistência de terra, parcerias, balcão virtual e apoio com IA.</td><td>training needs, land cases, partnerships, desk cases, ai runs</td></tr></table>
|
||||
|
||||
<h2>3. Actores e fronteiras de acesso</h2>
|
||||
<table><tr><th>Actor</th><th>O que pretende</th><th>Zona típica</th></tr>
|
||||
<tr><td>Visitante</td><td>Consultar conteúdos públicos e iniciar interesse, apoio ou simulação.</td><td>Portal público</td></tr>
|
||||
<tr><td>Investidor / utilizador registado</td><td>Manter perfil, favoritos, rascunhos, submissões, anexos e mensagens.</td><td>Área autenticada</td></tr>
|
||||
<tr><td>Promotor / proprietário de negócio</td><td>Registar ou actualizar negócio e responder a pedidos de informação.</td><td>Área autenticada e workflow de revisão</td></tr>
|
||||
<tr><td>Gestor de portal / CRM</td><td>Rever interesses, qualificar leads, atribuir tarefas e gerir conteúdos.</td><td>Gestão interna</td></tr>
|
||||
<tr><td>Gestor territorial</td><td>Manter projectos, relações, zonas, geometria e referências territoriais.</td><td>Gestão interna</td></tr>
|
||||
<tr><td>Revisor documental / gestor fiscal</td><td>Validar metadados, evidência, versões e regras sob as respectivas funções.</td><td>Gestão restrita</td></tr>
|
||||
<tr><td>Agente de serviço / parceiro institucional</td><td>Tratar casos ou colaborar em parcerias e salas.</td><td>Serviços e colaboração</td></tr>
|
||||
<tr><td>Administrador / worker</td><td>Gerir segurança/configuração ou executar trabalho assíncrono autorizado.</td><td>Administração / sistema</td></tr></table>
|
||||
<p class="note">Os nomes acima são papéis funcionais consolidados. A autorização efectiva depende das policies, middleware, MFA, configuração e permissões em vigor.</p>
|
||||
|
||||
<h2>4. Diagrama de casos de uso com actores</h2>
|
||||
<p>O diagrama destaca actores (“bonecos”) e os principais serviços que usam. As associações mostram interacções típicas; não indicam que todos os serviços estejam activos na mesma instalação.</p>
|
||||
<div class="diagram">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1050 610" role="img" aria-label="Diagrama UML de casos de uso InvestMoz com actores">
|
||||
<defs><style>.actor{stroke:#173b60;stroke-width:3;fill:none}.line{stroke:#728397;stroke-width:1.7}.uc{fill:#eef6fc;stroke:#2778b8;stroke-width:2}.txt{font:16px Arial;fill:#17324d}.label{font:bold 15px Arial;fill:#17324d}.boundary{fill:#fbfdff;stroke:#9bb0c3;stroke-width:2;stroke-dasharray:8 5}</style></defs>
|
||||
<rect x="220" y="22" width="795" height="558" rx="14" class="boundary"/><text x="245" y="52" class="label">Sistema InvestMoz</text>
|
||||
<!-- stick actors -->
|
||||
<g class="actor"><circle cx="95" cy="115" r="17"/><path d="M95 132v65m-30-38h60m-30 38-27 42m27-42 27 42"/></g><text x="50" y="245" class="label">Visitante</text>
|
||||
<g class="actor"><circle cx="95" cy="335" r="17"/><path d="M95 352v65m-30-38h60m-30 38-27 42m27-42 27 42"/></g><text x="37" y="465" class="label">Investidor</text>
|
||||
<g class="actor"><circle cx="95" cy="510" r="17"/><path d="M95 527v45m-30-25h60m-30 25-22 33m22-33 22 33"/></g><text x="15" y="606" class="label">Gestor</text>
|
||||
<g class="actor"><circle cx="1025" cy="165" r="15"/><path d="M1025 180v55m-25-33h50m-25 33-22 36m22-36 22 36"/></g><text x="979" y="267" class="label">Revisor</text>
|
||||
<g class="actor"><circle cx="1025" cy="400" r="15"/><path d="M1025 415v55m-25-33h50m-25 33-22 36m22-36 22 36"/></g><text x="967" y="502" class="label">Parceiro</text>
|
||||
<!-- use cases -->
|
||||
<g><ellipse cx="410" cy="112" rx="145" ry="31" class="uc"/><text x="410" y="118" text-anchor="middle" class="txt">Consultar catálogo e mapa</text>
|
||||
<ellipse cx="730" cy="112" rx="145" ry="31" class="uc"/><text x="730" y="118" text-anchor="middle" class="txt">Pesquisar oportunidade/negócio</text>
|
||||
<ellipse cx="410" cy="205" rx="145" ry="31" class="uc"/><text x="410" y="211" text-anchor="middle" class="txt">Manifestar interesse</text>
|
||||
<ellipse cx="730" cy="205" rx="145" ry="31" class="uc"/><text x="730" y="211" text-anchor="middle" class="txt">Executar simulação</text>
|
||||
<ellipse cx="410" cy="300" rx="145" ry="31" class="uc"/><text x="410" y="306" text-anchor="middle" class="txt">Criar candidatura / dossier</text>
|
||||
<ellipse cx="730" cy="300" rx="145" ry="31" class="uc"/><text x="730" y="306" text-anchor="middle" class="txt">Anexar e acompanhar documentos</text>
|
||||
<ellipse cx="410" cy="397" rx="145" ry="31" class="uc"/><text x="410" y="403" text-anchor="middle" class="txt">Tratar interesse no CRM</text>
|
||||
<ellipse cx="730" cy="397" rx="145" ry="31" class="uc"/><text x="730" y="403" text-anchor="middle" class="txt">Validar e publicar conteúdo</text>
|
||||
<ellipse cx="410" cy="495" rx="145" ry="31" class="uc"/><text x="410" y="501" text-anchor="middle" class="txt">Gerir território e zonas</text>
|
||||
<ellipse cx="730" cy="495" rx="145" ry="31" class="uc"/><text x="730" y="501" text-anchor="middle" class="txt">Colaborar em parceria/serviço</text></g>
|
||||
<!-- actor associations -->
|
||||
<g class="line"><path d="M112 115H264M112 115 585 112M112 145 265 195M112 355 265 300M112 370 265 300M112 388 585 300M112 515 265 397M112 525 265 495M112 540 585 495M1008 175 875 397M1008 190 875 300M1008 410 875 495"/></g>
|
||||
</svg></div>
|
||||
<p class="legend">Exemplo do percurso mais comum: visitante pesquisa → manifesta interesse → investidor cria/acompanha candidatura → gestão trata o pedido → revisão e decisão.</p>
|
||||
|
||||
<h2>5. Casos de uso reais — jornadas ilustrativas</h2>
|
||||
<h3>UC-A — Investidor encontra oportunidade e manifesta interesse</h3>
|
||||
<ol><li>O visitante consulta sectores, filtra oportunidades por localização e abre o detalhe.</li><li>Escolhe “manifestar interesse”, informa contacto, modalidade e mensagem, e aceita o consentimento aplicável.</li><li>O sistema valida o alvo, regista o pedido e, quando configurado, um recibo/idempotência.</li><li>O investidor pode autenticar-se para guardar a oportunidade ou avançar para uma submissão.</li><li>Um gestor consulta o pedido, verifica consentimentos, atribui responsável, qualifica e cria tarefa de seguimento.</li><li>As alterações relevantes ficam no histórico; notificações e integrações só são consideradas entregues quando o disparo e o serviço estiverem confirmados.</li></ol>
|
||||
<p><b>Resultado:</b> pedido rastreável ligado à oportunidade e encaminhado para tratamento. <b>Entidades:</b> Opportunity, Investor/User, InterestRequest, Consent, Lead, Task, AuditEvent.</p>
|
||||
<h3>UC-B — Proprietário submete um negócio para divulgação</h3>
|
||||
<ol><li>O proprietário cria rascunho e informa modalidade, objectos, localização e documentos de suporte.</li><li>Submete o registo; o estado passa para revisão conforme o workflow aplicável.</li><li>O gestor analisa e pode aprovar, rejeitar ou pedir informação complementar.</li><li>O revisor documental confere original, hash, versão, scan e autorização de divulgação.</li><li>Só após as validações necessárias o conteúdo pode ser publicado ao público; publicação não altera a situação jurídica do documento.</li></ol>
|
||||
<p><b>Resultado:</b> negócio com histórico e decisão rastreável. <b>Estados documentados:</b> draft → submitted → in_review → approved → published, com ramos needs_information, rejected, withdrawn, suspended e archived.</p>
|
||||
<h3>UC-C — Gestor territorial valida zona e relações</h3>
|
||||
<ol><li>O gestor regista a zona e a referência/fonte pertinente.</li><li>Associa localização, operador, categoria, geometria e relações com projectos, clusters ou corredores.</li><li>As relações sem evidência verificada permanecem pendentes e identificadas.</li><li>Um revisor valida a evidência e as regras de publicação; alterações ficam versionadas.</li><li>A zona só aparece no portal conforme estado e autorização de divulgação.</li></ol>
|
||||
<h3>UC-D — Utilizador guarda um rascunho e executa simulação</h3>
|
||||
<ol><li>O utilizador preenche o formulário; os dados incompletos podem ser guardados como rascunho recuperável.</li><li>Quando solicita o cálculo, o sistema selecciona questionário e parâmetros elegíveis.</li><li>O job de simulação executa após a transacção e grava cenários, resultados, obrigações e fontes usadas.</li><li>O relatório pode ser consultado; o rascunho continua distinto do cálculo executado.</li></ol>
|
||||
<div class="callout"><b>Regra de integridade:</b> importar ou publicar fonte documental não activa automaticamente uma regra fiscal. Parâmetros ausentes/ambíguos permanecem indeterminados.</div>
|
||||
<h3>UC-E — Parceiros colaboram numa parceria ou serviço</h3>
|
||||
<p>Um actor cria uma proposta; outro manifesta interesse; após aceitação, os participantes podem colaborar numa sala, trocar mensagens/documentos e acompanhar marcos. O balcão virtual segue padrão semelhante para pedidos de serviço: registo, análise, pedido de documentos, eventos, revisão e resolução.</p>
|
||||
|
||||
<h2>6. Diagrama de classes (visão lógica)</h2>
|
||||
<p>O diagrama apresenta as classes de domínio principais, atributos representativos e operações. É uma vista funcional; não reproduz todos os campos nem métodos Laravel.</p>
|
||||
<div class="diagram"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1100 690" role="img" aria-label="Diagrama UML de classes InvestMoz">
|
||||
<defs><style>.box{fill:#fff;stroke:#276c9b;stroke-width:2}.head{fill:#e8f2f9}.ct{font:14px Arial;fill:#17324d}.ch{font:bold 15px Arial;fill:#123d68}.rel{stroke:#60768b;stroke-width:1.8;fill:none}.mult{font:12px Arial;fill:#455b70}</style></defs>
|
||||
<!-- classes -->
|
||||
<g><rect x="30" y="30" width="220" height="120" rx="5" class="box"/><rect x="30" y="30" width="220" height="32" class="head"/><text x="140" y="52" text-anchor="middle" class="ch">User</text><text x="44" y="82" class="ct">id, email, verified_at</text><text x="44" y="105" class="ct">2FA / passkeys</text><text x="44" y="132" class="ct">+ authenticate()</text>
|
||||
<rect x="315" y="30" width="220" height="120" rx="5" class="box"/><rect x="315" y="30" width="220" height="32" class="head"/><text x="425" y="52" text-anchor="middle" class="ch">Investor</text><text x="329" y="82" class="ct">id, user_id, profile</text><text x="329" y="105" class="ct">company interests</text><text x="329" y="132" class="ct">+ manageProfile()</text>
|
||||
<rect x="600" y="30" width="220" height="120" rx="5" class="box"/><rect x="600" y="30" width="220" height="32" class="head"/><text x="710" y="52" text-anchor="middle" class="ch">Opportunity</text><text x="614" y="82" class="ct">sector, slug, status</text><text x="614" y="105" class="ct">location, investment</text><text x="614" y="132" class="ct">+ publish()</text>
|
||||
<rect x="865" y="30" width="205" height="120" rx="5" class="box"/><rect x="865" y="30" width="205" height="32" class="head"/><text x="967" y="52" text-anchor="middle" class="ch">Sector</text><text x="879" y="82" class="ct">id, name, slug</text><text x="879" y="105" class="ct">active / published</text><text x="879" y="132" class="ct">+ listOpportunities()</text>
|
||||
<rect x="30" y="230" width="235" height="130" rx="5" class="box"/><rect x="30" y="230" width="235" height="32" class="head"/><text x="147" y="252" text-anchor="middle" class="ch">InterestRequest</text><text x="44" y="282" class="ct">target, contact, consent</text><text x="44" y="305" class="ct">status, submitted_at</text><text x="44" y="338" class="ct">+ submit() / qualify()</text>
|
||||
<rect x="325" y="230" width="235" height="130" rx="5" class="box"/><rect x="325" y="230" width="235" height="32" class="head"/><text x="442" y="252" text-anchor="middle" class="ch">Lead / Task</text><text x="339" y="282" class="ct">owner, next_action</text><text x="339" y="305" class="ct">due_at, status</text><text x="339" y="338" class="ct">+ assign() / complete()</text>
|
||||
<rect x="620" y="230" width="235" height="130" rx="5" class="box"/><rect x="620" y="230" width="235" height="32" class="head"/><text x="737" y="252" text-anchor="middle" class="ch">Submission</text><text x="634" y="282" class="ct">owner, target, state</text><text x="634" y="305" class="ct">revisions, needs</text><text x="634" y="338" class="ct">+ save() / submit()</text>
|
||||
<rect x="910" y="230" width="160" height="130" rx="5" class="box"/><rect x="910" y="230" width="160" height="32" class="head"/><text x="990" y="252" text-anchor="middle" class="ch">Document</text><text x="924" y="282" class="ct">hash, source</text><text x="924" y="305" class="ct">version, status</text><text x="924" y="338" class="ct">+ scan()</text>
|
||||
<rect x="30" y="450" width="235" height="130" rx="5" class="box"/><rect x="30" y="450" width="235" height="32" class="head"/><text x="147" y="472" text-anchor="middle" class="ch">Project / Zone</text><text x="44" y="502" class="ct">territory, geometry</text><text x="44" y="525" class="ct">relations, evidence</text><text x="44" y="558" class="ct">+ validate() / publish()</text>
|
||||
<rect x="325" y="450" width="235" height="130" rx="5" class="box"/><rect x="325" y="450" width="235" height="32" class="head"/><text x="442" y="472" text-anchor="middle" class="ch">Simulation</text><text x="339" y="502" class="ct">questionnaire, input</text><text x="339" y="525" class="ct">scenario, result, sources</text><text x="339" y="558" class="ct">+ execute() / report()</text>
|
||||
<rect x="620" y="450" width="235" height="130" rx="5" class="box"/><rect x="620" y="450" width="235" height="32" class="head"/><text x="737" y="472" text-anchor="middle" class="ch">TaxRuleVersion</text><text x="634" y="502" class="ct">source, dates, parameters</text><text x="634" y="525" class="ct">review / approval state</text><text x="634" y="558" class="ct">+ resolveForPeriod()</text>
|
||||
<rect x="910" y="450" width="160" height="130" rx="5" class="box"/><rect x="910" y="450" width="160" height="32" class="head"/><text x="990" y="472" text-anchor="middle" class="ch">AuditEvent</text><text x="924" y="502" class="ct">actor, entity</text><text x="924" y="525" class="ct">action, time</text><text x="924" y="558" class="ct">+ record()</text></g>
|
||||
<!-- relations -->
|
||||
<g class="rel"><path d="M250 90H315M535 90H600M820 90H865M140 150V230M425 150V230M710 150V230M265 295H325M560 295H620M855 295H910M140 360V450M442 360V450M737 360V450M855 515H910M250 130 900 470"/></g>
|
||||
<g class="mult"><text x="270" y="82">1 : 0..1</text><text x="547" y="82">0..*</text><text x="834" y="82">1</text><text x="148" y="205">1 : 0..*</text><text x="273" y="286">0..1</text><text x="567" y="286">1..*</text><text x="861" y="286">0..*</text><text x="145" y="435">1 : 0..*</text><text x="450" y="435">1 : 0..*</text></g>
|
||||
</svg></div>
|
||||
<p class="note">A relação exacta Investor/User, a associação Submission/target e os vínculos Document/entidade podem ser opcionais ou implementados por tabelas polimórficas/de junção; confirme no schema antes de gerar código ou migrações.</p>
|
||||
|
||||
<h2>7. Diagrama de relacionamento (ER simplificado)</h2>
|
||||
<div class="diagram"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1080 560" role="img" aria-label="Diagrama de relacionamento das entidades InvestMoz">
|
||||
<defs><style>.e{fill:#edf5fb;stroke:#2477b6;stroke-width:2}.et{font:bold 15px Arial;fill:#123d68}.r{stroke:#61788d;stroke-width:2;fill:none}.rt{font:12px Arial;fill:#40566b}</style></defs>
|
||||
<g><rect x="35" y="45" width="155" height="60" rx="9" class="e"/><text x="112" y="81" text-anchor="middle" class="et">SECTOR</text><rect x="260" y="45" width="180" height="60" rx="9" class="e"/><text x="350" y="81" text-anchor="middle" class="et">OPPORTUNITY</text><rect x="515" y="45" width="190" height="60" rx="9" class="e"/><text x="610" y="81" text-anchor="middle" class="et">INTEREST_REQUEST</text><rect x="805" y="45" width="160" height="60" rx="9" class="e"/><text x="885" y="81" text-anchor="middle" class="et">INVESTOR</text>
|
||||
<rect x="35" y="205" width="155" height="60" rx="9" class="e"/><text x="112" y="241" text-anchor="middle" class="et">USER</text><rect x="260" y="205" width="180" height="60" rx="9" class="e"/><text x="350" y="241" text-anchor="middle" class="et">SUBMISSION</text><rect x="515" y="205" width="190" height="60" rx="9" class="e"/><text x="610" y="241" text-anchor="middle" class="et">DOCUMENT</text><rect x="805" y="205" width="160" height="60" rx="9" class="e"/><text x="885" y="241" text-anchor="middle" class="et">AUDIT_EVENT</text>
|
||||
<rect x="35" y="365" width="155" height="60" rx="9" class="e"/><text x="112" y="401" text-anchor="middle" class="et">PROJECT</text><rect x="260" y="365" width="180" height="60" rx="9" class="e"/><text x="350" y="401" text-anchor="middle" class="et">CLUSTER</text><rect x="515" y="365" width="190" height="60" rx="9" class="e"/><text x="610" y="401" text-anchor="middle" class="et">INVESTMENT_ZONE</text><rect x="805" y="365" width="160" height="60" rx="9" class="e"/><text x="885" y="401" text-anchor="middle" class="et">LEGAL_SOURCE</text>
|
||||
<rect x="145" y="490" width="180" height="55" rx="9" class="e"/><text x="235" y="523" text-anchor="middle" class="et">SIMULATION</text><rect x="450" y="490" width="180" height="55" rx="9" class="e"/><text x="540" y="523" text-anchor="middle" class="et">TAX_RULE_VERSION</text><rect x="755" y="490" width="180" height="55" rx="9" class="e"/><text x="845" y="523" text-anchor="middle" class="et">SIMULATION_RESULT</text></g>
|
||||
<g class="r"><path d="M190 75H260M440 75H515M705 75H805M190 235H260M440 235H515M705 235H805M190 395H260M440 395H515M705 395H805M235 425V490M325 518H450M630 518H755M350 105V205M610 105V205M610 265V365M885 105V205M885 425V455H540V490"/></g>
|
||||
<g class="rt"><text x="210" y="67">1 : N</text><text x="465" y="67">1 : N</text><text x="730" y="67">N : 0..1</text><text x="210" y="227">1 : N</text><text x="462" y="227">N : M* via links</text><text x="730" y="227">1 : N</text><text x="210" y="387">N : M*</text><text x="462" y="387">N : M*</text><text x="730" y="387">1 : N</text><text x="235" y="466">1 : N</text><text x="346" y="510">1 : N</text><text x="650" y="510">1 : N</text><text x="354" y="160">1 : N</text><text x="617" y="160">association</text><text x="894" y="160">actor</text><text x="670" y="448">source</text></g>
|
||||
</svg></div>
|
||||
<p class="note">N:M = muitos-para-muitos, normalmente resolvido por tabela de associação. Ligações genéricas de documentos/auditoria podem não ser FKs directas. Este mapa é uma vista funcional, não um substituto do ERD integral em docs/investmoz/erd.mmd e do dicionário de dados.</p>
|
||||
|
||||
<h2>8. Eventos e frequência</h2>
|
||||
<table><tr><th>Evento/processo</th><th>Gatilho</th><th>Frequência confirmada</th><th>Leitura operacional</th></tr>
|
||||
<tr><td>Escalar tarefas vencidas</td><td>Scheduler Laravel</td><td>A cada 15 minutos; sem sobreposição configurada</td><td>Cadência declarada em routes/console.php. O scheduler do servidor tem de estar activo.</td></tr>
|
||||
<tr><td>Executar simulação (RunSimulation)</td><td>Pedido de cálculo aceite</td><td>Por pedido, via fila, afterCommit</td><td>Assíncrono; volume depende da utilização e dos workers.</td></tr>
|
||||
<tr><td>Scan de malware (ScanDocumentForMalware)</td><td>Upload ou pedido de análise em fluxos suportados</td><td>Por documento, via fila; job único</td><td>Depende de worker e scanner. Upload não prova análise concluída.</td></tr>
|
||||
<tr><td>Comparação de compatibilidade MNE</td><td>Alteração aplicável do negócio</td><td>Por alteração aplicável, em fila de sombra</td><td>Job identificado; frequência de alterações não medida.</td></tr>
|
||||
<tr><td>Notificação de interesse</td><td>Recepção de interesse</td><td>Disparo não confirmado</td><td>Job NotifyInterestReceived existe, mas não foi localizada chamada dispatch().</td></tr>
|
||||
<tr><td>Outbox/inbox e sincronização externa</td><td>Evento/mensagem externa</td><td>Sem calendário operacional confirmado</td><td>Estrutura preparada; documentação indica sincronização real pendente.</td></tr>
|
||||
<tr><td>Pesquisas, submissões, revisões e auditoria</td><td>Acção do visitante/utilizador/gestor</td><td>Sob procura ou acção; contagem indisponível</td><td>Sem logs/analytics não se calculam ocorrências por período.</td></tr></table>
|
||||
<p><b>Frequência configurada não é frequência observada.</b> Não foram disponibilizados logs, contagens de eventos da base activa, filas, analytics nem tráfego HTTP. Assim, eventos por hora/dia/mês, sazonalidade, falhas e utilizadores activos permanecem indeterminados. Não se extrapolam estes valores a partir de testes, seeders, screenshots ou schema.</p>
|
||||
|
||||
<h2>9. Inventário dos 24 casos de uso</h2>
|
||||
<table><tr><th>Grupo</th><th>Casos</th></tr>
|
||||
<tr><td>Portal e catálogo</td><td>UC-01 Consultar página inicial e serviços; UC-02 Pesquisar sectores e oportunidades; UC-03 Consultar negócio existente.</td></tr>
|
||||
<tr><td>Conta e jornada</td><td>UC-04 Criar conta/autenticar/recuperar acesso; UC-05 Manifestar interesse numa oportunidade; UC-06 Manifestar interesse num negócio; UC-07 Guardar oportunidade; UC-08 Manter submissão; UC-09 Submeter candidatura.</td></tr>
|
||||
<tr><td>Negócios e CRM</td><td>UC-10 Gerir negócio existente; UC-11 Tratar interesses no CRM.</td></tr>
|
||||
<tr><td>Território e documentos</td><td>UC-12 Gerir catálogo territorial; UC-13 Gerir zonas; UC-14 Carregar, verificar e publicar documento.</td></tr>
|
||||
<tr><td>Simulação/fiscalidade</td><td>UC-15 Criar rascunho; UC-16 Executar simulação; UC-17 Gerir regra fiscal.</td></tr>
|
||||
<tr><td>Serviços especializados</td><td>UC-18 Gerir educação e competências; UC-19 Assistência de terra; UC-20 Gerir parceria; UC-21 Usar balcão virtual.</td></tr>
|
||||
<tr><td>Transversal</td><td>UC-22 Usar IA com evidência; UC-23 Sincronizar sistemas externos; UC-24 Auditar alterações.</td></tr></table>
|
||||
<p>Os pré-requisitos, fluxos alternativos e tabelas associadas por UC encontram-se no ficheiro <i>casos-de-uso-aplicacao-actual.md</i>.</p>
|
||||
|
||||
<h2>10. Integridade, segurança e limitações</h2>
|
||||
<ul><li>Disponibilidade para consulta, situação jurídica/documental e uso em regra/cálculo são estados independentes.</li><li>Respeitar autorização de divulgação e âmbito; relações sem evidência ficam pendentes.</li><li>Preservar original, proveniência, ID exacto, edição/data, hash e histórico. Não confirmar autenticidade, vigência, direitos ou scan sem evidência.</li><li>Importação não é publicação; publicação documental não activa cálculo fiscal; rascunho não é candidatura aprovada.</li><li>Integração desenhada não significa sincronização activa. Cobertura e estado de cada domínio devem ser verificados nos respectivos relatórios de validação.</li><li>Antes de alterações a dados activos, confirmar backup/restauro e acção autenticada. Testes e migrações usam exclusivamente base QA isolada.</li></ul>
|
||||
|
||||
<h2>11. Fontes de análise</h2>
|
||||
<ul><li>README.md do repositório;</li><li>docs/investmoz/casos-de-uso-aplicacao-actual.md;</li><li>docs/investmoz/erd.mmd, data-dictionary.csv e table-catalog.csv;</li><li>rotas, controladores, modelos, jobs, comando de escalada e migrations;</li><li>InvestMoz — Levantamento funcional, casos de uso e diagramas.docx, em Downloads.</li></ul>
|
||||
<p class="note">O inventário cobriu 981 caminhos listados por rg --files, excluindo vendor e node_modules. Isso descreve o repositório acessível, não anexos externos, conteúdo de armazenamento privado ou base de dados de produção.</p>
|
||||
</body></html>
|
||||
@@ -0,0 +1,526 @@
|
||||
# Casos de uso da aplicação actual
|
||||
|
||||
Este documento descreve os processos actualmente implementados ou previstos
|
||||
pelas rotas, controladores, serviços e migrations existentes no projecto.
|
||||
Não substitui regras legais nem autoriza publicação de dados.
|
||||
|
||||
## 1. Actores
|
||||
|
||||
| Actor | Responsabilidade |
|
||||
|---|---|
|
||||
| Visitante | Consulta o portal, oportunidades, negócios, zonas e documentos públicos. |
|
||||
| Utilizador registado | Mantém perfil, rascunhos, submissões, favoritos e mensagens. |
|
||||
| Investidor | Pesquisa oportunidades, manifesta interesse e acompanha candidaturas. |
|
||||
| Gestor | Trata interesses, leads, tarefas, oportunidades e catálogo. |
|
||||
| Revisor documental | Verifica documentos, malware, versões e autorização de divulgação. |
|
||||
| Gestor territorial | Gere projectos, clusters, corredores, geografia e zonas. |
|
||||
| Gestor fiscal | Gere fontes legais, versões de regras e parâmetros do simulador. |
|
||||
| Gestor de educação | Gere competências, necessidades, cursos e propostas. |
|
||||
| Parceiro institucional | Participa em parcerias, salas, mensagens, documentos e marcos. |
|
||||
| Administrador | Gere utilizadores, permissões, configurações e instalação de módulos. |
|
||||
| Sistema externo | Recebe/envia eventos através de outbox, inbox e sincronizações. |
|
||||
|
||||
## 2. Visão geral do ciclo da aplicação
|
||||
|
||||
```text
|
||||
Portal público
|
||||
-> consulta de catálogo
|
||||
-> interesse, apoio ou simulação
|
||||
-> autenticação/conta quando necessário
|
||||
-> candidatura ou submissão
|
||||
-> documentos e validação
|
||||
-> CRM, tarefas e comunicação
|
||||
-> decisão, parceria ou acompanhamento
|
||||
```
|
||||
|
||||
Os principais agregados são `opportunities`, `existing_businesses`,
|
||||
`interest_requests`, `investment_submissions`, `documents`, `investment_zones`
|
||||
e `simulations`.
|
||||
|
||||
## 3. UC-01 — Consultar a página inicial e serviços
|
||||
|
||||
**Actor:** Visitante.
|
||||
|
||||
**Pré-condições:** O portal está disponível.
|
||||
|
||||
**Fluxo principal:**
|
||||
|
||||
1. O visitante abre `/`.
|
||||
2. O sistema apresenta a página inicial e os serviços publicados.
|
||||
3. O visitante escolhe oportunidades, negócios, zonas, simulador, documentos,
|
||||
apoio ou parcerias.
|
||||
4. O sistema aplica apenas dados que estejam disponíveis para o público.
|
||||
|
||||
**Dados:** `portal_resources`, `documents`, `opportunities`, `sectors`,
|
||||
`existing_businesses`, `investment_zones`.
|
||||
|
||||
**Resultado:** O visitante é encaminhado para o processo escolhido.
|
||||
|
||||
## 4. UC-02 — Pesquisar sectores e oportunidades
|
||||
|
||||
**Actor:** Visitante ou investidor.
|
||||
|
||||
**Fluxo principal:**
|
||||
|
||||
1. O actor consulta `/oportunidades` ou a API de oportunidades.
|
||||
2. O sistema filtra por sector, localização, slug, estado e outros critérios.
|
||||
3. O detalhe da oportunidade mostra perfil, território, relações, documentos
|
||||
públicos e informação de investimento.
|
||||
4. O actor pode iniciar uma manifestação de interesse ou guardar a oportunidade.
|
||||
|
||||
**Dados:** `sectors`, `opportunities`, `opportunity_profiles`,
|
||||
`opportunity_facets`, `opportunity_clusters`, `opportunity_corridors`,
|
||||
`opportunity_projects`, `opportunity_locations`, `opportunity_favourites`.
|
||||
|
||||
**Resultado:** Consulta pública ou início de UC-05/UC-06.
|
||||
|
||||
## 5. UC-03 — Consultar um negócio existente
|
||||
|
||||
**Actor:** Visitante ou investidor.
|
||||
|
||||
**Fluxo principal:**
|
||||
|
||||
1. O actor consulta `/negocios`.
|
||||
2. O sistema lista negócios cujo estado e autorização permitem divulgação.
|
||||
3. O actor abre o detalhe pelo `slug`.
|
||||
4. O sistema apresenta apenas título, descrição, modalidade, localização,
|
||||
objectos e documentos autorizados.
|
||||
5. O actor pode iniciar uma manifestação de interesse.
|
||||
|
||||
**Dados:** `existing_businesses`, `existing_business_objects`,
|
||||
`existing_business_locations`, `existing_business_document_links`,
|
||||
`documents`, `interest_requests`.
|
||||
|
||||
**Regra:** Dados privados, mandatos e documentos não autorizados não são
|
||||
expostos pelo portal público.
|
||||
|
||||
## 6. UC-04 — Criar conta, autenticar e recuperar acesso
|
||||
|
||||
**Actor:** Visitante/utilizador.
|
||||
|
||||
**Fluxo principal:**
|
||||
|
||||
1. O visitante cria uma conta com nome, email e palavra-passe.
|
||||
2. O sistema cria `users`.
|
||||
3. O utilizador confirma o email quando aplicável.
|
||||
4. O utilizador inicia sessão.
|
||||
5. O sistema aplica as políticas de autenticação, confirmação de palavra-passe
|
||||
e, para operações protegidas, segundo factor.
|
||||
|
||||
**Dados:** `users`, `passkeys`, `sessions`, `password_reset_tokens`.
|
||||
|
||||
**Alternativas:** Registo rejeitado por email existente; palavra-passe
|
||||
incorrecta; recuperação por token; desafio de dois factores.
|
||||
|
||||
## 7. UC-05 — Manifestar interesse numa oportunidade
|
||||
|
||||
**Actor:** Visitante ou investidor.
|
||||
|
||||
**Fluxo principal:**
|
||||
|
||||
1. O actor abre o formulário de interesse.
|
||||
2. Informa contacto, oportunidade, modalidade, valor e mensagem.
|
||||
3. O sistema valida os dados, consentimentos e alvo.
|
||||
4. É criado `interest_requests`.
|
||||
5. O sistema regista recibo/idempotência quando aplicável.
|
||||
6. O sistema envia notificação ou evento de tratamento.
|
||||
|
||||
**Dados:** `interest_requests`, `consents`, `interest_submission_receipts`,
|
||||
`notifications`, `outbox_events`.
|
||||
|
||||
**Estado inicial:** `received`, `submitted` ou equivalente configurado.
|
||||
|
||||
**Resultado:** O interesse fica disponível para tratamento no CRM.
|
||||
|
||||
## 8. UC-06 — Manifestar interesse num negócio existente
|
||||
|
||||
**Actor:** Investidor.
|
||||
|
||||
**Fluxo principal:**
|
||||
|
||||
1. O investidor escolhe um `existing_business` publicado.
|
||||
2. Selecciona a modalidade e, se aplicável, percentagem e intervalo de valor.
|
||||
3. O sistema verifica que o negócio pode receber interesse.
|
||||
4. Cria `interest_requests.existing_business_id`.
|
||||
5. Regista consentimento para contacto e partilha da informação.
|
||||
6. O caso passa para tratamento interno.
|
||||
|
||||
**Dados:** `existing_businesses`, `interest_requests`, `investors`, `consents`.
|
||||
|
||||
**Regra:** Um pedido não pode apontar simultaneamente para uma oportunidade e
|
||||
para um negócio existente.
|
||||
|
||||
## 9. UC-07 — Guardar uma oportunidade e manter espaço do investidor
|
||||
|
||||
**Actor:** Utilizador autenticado.
|
||||
|
||||
**Fluxo principal:**
|
||||
|
||||
1. O utilizador abre uma oportunidade.
|
||||
2. Escolhe guardar/iniciar no espaço do investidor.
|
||||
3. O sistema regista a relação em `opportunity_favourites` ou inicia uma
|
||||
submissão/dossier.
|
||||
4. O utilizador consulta os seus itens em `/investidor` ou `/carteira`.
|
||||
|
||||
**Dados:** `users`, `opportunity_favourites`, `investment_dossiers`,
|
||||
`investor_accounts`, `investment_accounts`.
|
||||
|
||||
## 10. UC-08 — Criar e manter uma submissão de investimento
|
||||
|
||||
**Actor:** Investidor.
|
||||
|
||||
**Pré-condições:** Utilizador autenticado e verificado.
|
||||
|
||||
**Fluxo principal:**
|
||||
|
||||
1. O investidor cria uma submissão.
|
||||
2. O sistema cria `investment_submissions` com estado `draft`.
|
||||
3. O investidor grava o payload e as necessidades específicas.
|
||||
4. Pode adicionar anexos e mensagens.
|
||||
5. O sistema cria eventos e revisões para manter histórico.
|
||||
6. O investidor consulta ou actualiza a submissão.
|
||||
|
||||
**Dados:** `investment_submissions`, `investment_specific_needs`,
|
||||
`investment_submission_attachments`, `investment_submission_events`,
|
||||
`investment_submission_revisions`, `investment_messages`, `documents`.
|
||||
|
||||
## 11. UC-09 — Submeter candidatura
|
||||
|
||||
**Actor:** Investidor.
|
||||
|
||||
**Fluxo principal:**
|
||||
|
||||
1. O investidor confirma os dados da submissão.
|
||||
2. O sistema verifica campos obrigatórios, alvo e anexos.
|
||||
3. A submissão muda de `draft` para `submitted`.
|
||||
4. É associado ou criado o pedido de interesse, quando exigido.
|
||||
5. O sistema cria evento de submissão e notifica a gestão.
|
||||
|
||||
**Resultado:** A candidatura deixa de ser apenas rascunho e entra na fila de
|
||||
tratamento interno.
|
||||
|
||||
## 12. UC-10 — Gerir um negócio existente
|
||||
|
||||
**Actor:** Investidor, proprietário ou gestor.
|
||||
|
||||
**Fluxo principal:**
|
||||
|
||||
1. O actor cria ou actualiza o negócio.
|
||||
2. Regista modalidade, descrição, objectos, localização e documentos.
|
||||
3. O sistema cria revisões e mantém o snapshot anterior.
|
||||
4. Um gestor pode solicitar informação, rever ou rejeitar.
|
||||
5. O negócio percorre o workflow configurado.
|
||||
6. Apenas após validação e autorização poderá ser publicado.
|
||||
|
||||
**Dados:** `existing_businesses`, `existing_business_objects`,
|
||||
`existing_business_revisions`, `existing_business_reviews`,
|
||||
`business_workflow_transitions`, `existing_business_mandates`, `documents`.
|
||||
|
||||
**Estados possíveis:** `draft`, `submitted`, `in_review`,
|
||||
`needs_information`, `approved`, `published`, `suspended`, `rejected`,
|
||||
`withdrawn`, `archived`.
|
||||
|
||||
## 13. UC-11 — Tratar interesses no CRM
|
||||
|
||||
**Actor:** Gestor.
|
||||
|
||||
**Fluxo principal:**
|
||||
|
||||
1. O gestor abre a fila de interesses.
|
||||
2. Consulta o pedido, consentimentos e alvo.
|
||||
3. Atribui o caso a um responsável.
|
||||
4. Converte o interesse em `leads`, quando aplicável.
|
||||
5. Cria tarefas e actividades.
|
||||
6. Actualiza o estado e regista o resultado.
|
||||
7. Pode associar um `crm_opportunity` e continuar a negociação.
|
||||
|
||||
**Dados:** `interest_requests`, `leads`, `investors`, `contacts`,
|
||||
`crm_opportunities`, `tasks`, `activities`, `consents`.
|
||||
|
||||
**Estados de interesse:** recebimento, qualificação, contacto, diligência,
|
||||
negociação, conversão, recusa, cancelamento ou encerramento.
|
||||
|
||||
## 14. UC-12 — Gerir catálogo territorial
|
||||
|
||||
**Actor:** Gestor territorial.
|
||||
|
||||
**Fluxo principal:**
|
||||
|
||||
1. O gestor cria ou actualiza projectos, clusters e corredores.
|
||||
2. Liga projectos a locais.
|
||||
3. Liga projectos a clusters e corredores.
|
||||
4. Cria cadeias de valor e lacunas de investimento.
|
||||
5. Regista a fonte e o estado da relação.
|
||||
6. Outro gestor pode validar ou revogar a relação.
|
||||
|
||||
**Dados:** `projects`, `economic_clusters`, `value_chains`,
|
||||
`value_chain_links`, `development_corridors`, `cluster_projects`,
|
||||
`corridor_projects`, `corridor_clusters`, `investment_gaps`, `geo_locations`.
|
||||
|
||||
## 15. UC-13 — Gerir zonas de investimento
|
||||
|
||||
**Actor:** Gestor territorial com permissão e MFA quando exigido.
|
||||
|
||||
**Fluxo principal:**
|
||||
|
||||
1. Regista a fonte legal em `investment_legal_sources`.
|
||||
2. Cria a zona em `investment_zones`.
|
||||
3. Associa operador, categoria e localização.
|
||||
4. Adiciona geometria, anexos e relações com projectos, clusters ou corredores.
|
||||
5. Um revisor valida a informação.
|
||||
6. A zona é publicada, suspensa ou revogada.
|
||||
7. O sistema mantém revisões imutáveis.
|
||||
|
||||
**Estados:** `draft`, `published`, `suspended`; validação `pending`,
|
||||
`validated`, `historical`.
|
||||
|
||||
**Dados:** `investment_zones`, `investment_legal_sources`,
|
||||
`investment_zone_geometries`, `investment_zone_relations`,
|
||||
`investment_zone_links`, `investment_zone_attachments`, `investment_zone_revisions`.
|
||||
|
||||
## 16. UC-14 — Carregar, verificar e publicar documento
|
||||
|
||||
**Actor:** Gestor documental.
|
||||
|
||||
**Fluxo principal:**
|
||||
|
||||
1. O gestor carrega o ficheiro.
|
||||
2. O sistema preserva o original e calcula o hash.
|
||||
3. Detecta duplicados e conflitos de versão.
|
||||
4. Regista `documents` e, quando necessário, `document_versions`.
|
||||
5. Executa análise antimalware.
|
||||
6. O gestor revê classificação, datas, fonte e autorização.
|
||||
7. O documento é associado ao negócio, candidatura, zona, projecto ou portal.
|
||||
8. Só então pode ser disponibilizado publicamente.
|
||||
|
||||
**Dados:** `documents`, `document_versions`, `document_malware_scans`,
|
||||
`portal_resources`, `portal_resource_revisions` e tabelas `*_documents`.
|
||||
|
||||
**Estados documentais:** pendente, limpo, infectado, rejeitado, privado,
|
||||
publicável, publicado ou revogado.
|
||||
|
||||
## 17. UC-15 — Criar rascunho de simulação
|
||||
|
||||
**Actor:** Visitante ou utilizador.
|
||||
|
||||
**Fluxo principal:**
|
||||
|
||||
1. O actor abre o simulador.
|
||||
2. O sistema cria ou recupera um cookie de recuperação.
|
||||
3. Guarda os dados em `simulation_form_drafts`.
|
||||
4. O actor pode continuar mais tarde.
|
||||
5. Um utilizador autenticado pode reclamar o rascunho.
|
||||
6. O rascunho pode ser ligado a um negócio existente, mediante autorização.
|
||||
|
||||
**Resultado:** Dados incompletos permanecem como rascunho e não são tratados
|
||||
como cálculo fiscal executado.
|
||||
|
||||
## 18. UC-16 — Executar simulação
|
||||
|
||||
**Actor:** Visitante ou utilizador.
|
||||
|
||||
**Fluxo principal:**
|
||||
|
||||
1. O actor submete os dados do simulador.
|
||||
2. O sistema escolhe a versão do questionário.
|
||||
3. Resolve os parâmetros fiscais elegíveis.
|
||||
4. Cria `simulations` e `simulation_scenarios`.
|
||||
5. Executa o cálculo através de job.
|
||||
6. Guarda resultados, obrigações e fontes.
|
||||
7. Gera relatório quando solicitado.
|
||||
8. Regista auditoria e idempotência.
|
||||
|
||||
**Dados:** `questionnaire_versions`, `simulations`, `simulation_scenarios`,
|
||||
`simulation_results`, `simulation_obligations`, `simulation_reports`,
|
||||
`simulation_jobs`, `simulation_result_sources`, `simulation_audit_events`.
|
||||
|
||||
## 19. UC-17 — Gerir regra fiscal
|
||||
|
||||
**Actor:** Autor, revisor, aprovador ou publicador fiscal.
|
||||
|
||||
**Fluxo principal:**
|
||||
|
||||
1. O autor cria uma versão da regra.
|
||||
2. Associa a regra a uma fonte legal.
|
||||
3. O revisor verifica expressão, datas e parâmetros.
|
||||
4. O aprovador decide.
|
||||
5. O publicador disponibiliza a versão.
|
||||
6. O sistema regista cada transição.
|
||||
|
||||
**Dados:** `simulation_tax_rule_versions`, `simulation_tax_rule_events`,
|
||||
`investment_legal_sources`, `users`.
|
||||
|
||||
**Regra:** Importar ou publicar um documento não activa automaticamente um
|
||||
cálculo fiscal.
|
||||
|
||||
## 20. UC-18 — Gerir educação e competências
|
||||
|
||||
**Actor:** Gestor de educação.
|
||||
|
||||
**Fluxo principal:**
|
||||
|
||||
1. Mantém catálogo de competências, profissões, instituições e cursos.
|
||||
2. Regista necessidades de formação de projectos.
|
||||
3. Associa ocupações e competências.
|
||||
4. Regista capacidades e oportunidades educativas.
|
||||
5. Analisa lacunas.
|
||||
6. Cria proposta de investimento educativo.
|
||||
|
||||
**Dados:** `skills`, `occupations`, `training_needs`, `training_providers`,
|
||||
`courses`, `education_opportunities`, `investment_gaps`,
|
||||
`education_investment_proposals` e tabelas `skills_edu`.
|
||||
|
||||
## 21. UC-19 — Abrir e tratar assistência de terra
|
||||
|
||||
**Actor:** Investidor e gestor de assistência.
|
||||
|
||||
**Fluxo principal:**
|
||||
|
||||
1. O investidor cria um caso de assistência.
|
||||
2. Regista localização, necessidade e documentos.
|
||||
3. O gestor analisa o caso.
|
||||
4. São registadas revisões, eventos e pedidos adicionais.
|
||||
5. O caso é encaminhado ou encerrado.
|
||||
|
||||
**Dados:** `land_assistance_cases`, `land_assistance_revisions`,
|
||||
`land_assistance_events`, `land_assistance_documents`, `documents`, `users`.
|
||||
|
||||
## 22. UC-20 — Criar e gerir parceria
|
||||
|
||||
**Actor:** Investidor, organização ou parceiro institucional.
|
||||
|
||||
**Fluxo principal:**
|
||||
|
||||
1. O actor cria um rascunho de parceria.
|
||||
2. Regista origem, sector, local e objectivo.
|
||||
3. Outro actor manifesta interesse.
|
||||
4. A candidatura é aceite ou recusada.
|
||||
5. É aberta uma sala de parceria.
|
||||
6. Os participantes trocam mensagens e documentos.
|
||||
7. São definidos marcos, pagamentos ou acordo.
|
||||
8. O sistema regista eventos e notificações.
|
||||
|
||||
**Dados:** `portal_partnerships`, `partnership_origins`,
|
||||
`partnership_locations`, `partnership_interests`, `partnership_rooms`,
|
||||
`partnership_messages`, `partnership_documents`, `partnership_milestones`,
|
||||
`partnership_events`, `partnership_notifications`.
|
||||
|
||||
## 23. UC-21 — Usar o balcão virtual
|
||||
|
||||
**Actor:** Utilizador autenticado e agente de atendimento.
|
||||
|
||||
**Fluxo principal:**
|
||||
|
||||
1. O utilizador abre um pedido no balcão.
|
||||
2. O sistema cria um caso e associa o serviço.
|
||||
3. O agente recebe o pedido.
|
||||
4. São trocadas mensagens e solicitados documentos.
|
||||
5. O caso passa por estados e revisões.
|
||||
6. Eventos, notificações e pagamentos são registados.
|
||||
7. O caso é resolvido ou encerrado.
|
||||
|
||||
**Dados:** `desk_service_versions`, `desk_cases`, `desk_requests`,
|
||||
`desk_documents`, `desk_events`, `desk_revisions`, `desk_messages`,
|
||||
`desk_notifications`, `desk_institutional_links`, `desk_payments`.
|
||||
|
||||
## 24. UC-22 — Usar inteligência artificial com evidência
|
||||
|
||||
**Actor:** Gestor autorizado.
|
||||
|
||||
**Fluxo principal:**
|
||||
|
||||
1. O gestor activa a capacidade em `ai_controls`.
|
||||
2. Regista uma fonte ou documento.
|
||||
3. O sistema cria chunks da fonte.
|
||||
4. O gestor inicia uma execução de IA.
|
||||
5. O sistema grava `ai_runs`.
|
||||
6. O resultado pode ser revisto ou receber feedback.
|
||||
7. A aplicação mantém a fonte que suporta a resposta.
|
||||
|
||||
**Dados:** `ai_controls`, `ai_sources`, `ai_chunks`, `ai_runs`, `ai_feedback`,
|
||||
`documents`, `users`.
|
||||
|
||||
## 25. UC-23 — Sincronizar com sistemas externos
|
||||
|
||||
**Actor:** Sistema externo ou processo de integração.
|
||||
|
||||
**Fluxo principal:**
|
||||
|
||||
1. A aplicação gera um evento em `outbox_events`.
|
||||
2. Um worker publica o evento.
|
||||
3. O sistema externo responde ou envia evento para `inbox_events`.
|
||||
4. A aplicação cria `sync_runs`.
|
||||
5. Falhas ou divergências são registadas em `reconciliation_issues`.
|
||||
6. O processo pode ser repetido de forma idempotente.
|
||||
|
||||
## 26. UC-24 — Auditoria e controlo de alterações
|
||||
|
||||
**Actor:** Sistema, gestor ou auditor.
|
||||
|
||||
**Fluxo principal:**
|
||||
|
||||
1. Uma operação protegida é executada.
|
||||
2. O sistema identifica actor, entidade, acção e correlação.
|
||||
3. Regista evento de auditoria.
|
||||
4. Alterações de entidades versionadas geram revisão/snapshot.
|
||||
5. O auditor consulta o histórico sem alterar o registo original.
|
||||
|
||||
**Dados:** `audit_events`, `simulation_audit_events`, `investment_zone_revisions`,
|
||||
`existing_business_revisions`, `investment_submission_revisions`,
|
||||
`desk_revisions`, `outbox_events`.
|
||||
|
||||
## 27. Regras transversais da aplicação
|
||||
|
||||
- Dados privados exigem autenticação e autorização.
|
||||
- Divulgação pública depende do estado de publicação e autorização registada.
|
||||
- Documentos originais devem ser preservados.
|
||||
- Hash, versão e proveniência devem permanecer associados ao documento.
|
||||
- Relações jurídicas ou territoriais não validadas ficam pendentes.
|
||||
- Rascunho não é submissão aprovada.
|
||||
- Importação não é publicação.
|
||||
- Publicação de documento não é activação de regra fiscal.
|
||||
- Cálculos fiscais devem usar fonte e parâmetro identificados.
|
||||
- Histórico e auditoria não devem ser apagados por rollback destrutivo.
|
||||
- Operações de migração e testes devem usar base QA isolada.
|
||||
|
||||
## 28. Mapa resumido dos processos
|
||||
|
||||
```text
|
||||
Visitante
|
||||
-> consulta catálogo
|
||||
-> interesse / apoio / simulação
|
||||
|
||||
Investidor
|
||||
-> conta
|
||||
-> favoritos / dossier
|
||||
-> submissão
|
||||
-> documentos
|
||||
-> mensagens
|
||||
|
||||
Gestor
|
||||
-> interesse
|
||||
-> lead
|
||||
-> tarefas
|
||||
-> oportunidade CRM
|
||||
-> decisão
|
||||
|
||||
Gestor territorial
|
||||
-> projecto / cluster / corredor
|
||||
-> zona
|
||||
-> geometria
|
||||
-> fonte legal
|
||||
-> validação / publicação
|
||||
|
||||
Gestor fiscal
|
||||
-> regra
|
||||
-> revisão
|
||||
-> aprovação
|
||||
-> publicação
|
||||
-> parâmetro usado pelo simulador
|
||||
|
||||
Parceiros
|
||||
-> parceria
|
||||
-> candidatura
|
||||
-> sala
|
||||
-> mensagens / documentos / marcos
|
||||
```
|
||||
@@ -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` |
|
||||
@@ -315,3 +319,37 @@ Situação comum observada: `publication_status=draft`, `validation_status=valid
|
||||
- Verificação: `php artisan test tests/Feature/PortalServicesTest.php` — 17 testes/497 asserções aprovados em QA; cobrem publicação autorizada com pesquisa textual e filtros combinados, ficha, download, associação ao módulo e links de requisitos com estados confirmados/pendentes. `npm.cmd run build` concluído. O build alerta sobre pacote opcional `fontaine` e chunk `services` acima de 500 KB; sem erro de compilação. Não houve browser autenticado disponível, portanto não se declara publicação, pesquisa ou download públicos concluídos.
|
||||
- A autorização documental não foi aplicada a zonas, parques, negócios privados ou oportunidades de demonstração. A regra de publicação de parque não exige área confirmada; divergência de área, isoladamente, não é um gate técnico. Se a ficha avançar por decisão própria, deve omitir o total disputado ou mostrar os valores reportados com fonte/escopo, sem alegar disponibilidade. Beluluane, Munhava e Topuito requerem revisão por registo para fonte/localização pública e para retirar ou qualificar a área; Topuito só tem província associada no mestre geográfico, pelo que não se pode anunciar uma associação administrativa a Larde enquanto essa referência não existir. ZEE/ZFI continuam dependentes de acto oficial estruturado e validado, regime e relações territoriais apropriadas; uma área sem confirmação não substitui essas evidências.
|
||||
- Proposta para as seis oportunidades de demonstração já públicas: preservar os registos e IDs, adicionar origem/tipo `demo` e identificá-los visivelmente como **Exemplo de demonstração**, separar a pesquisa de exemplos da pesquisa de oportunidades reais e excluir os exemplos dos resultados reais por defeito. Não os converter nem retirar em massa; cada registo real exigirá origem e proponente documentados. Nenhuma alteração foi feita nesta execução.
|
||||
|
||||
### Referência da entrada única de gestão — QA, 22/10/2026
|
||||
|
||||
Nos requisitos existentes `MASTER-9f6dd1d9-0001`, `MASTER-9f6dd1d9-0003` e `MASTER-02f44baf-0002`, acrescentar como evidência delimitada a secção «Entrada única de gestão — preparação em QA, 22/10/2026» do manual de rastreabilidade e `tests/Feature/ContentManagementEntryTest.php`: quatro testes/99 asserções, ClamAV real e reversão transaccional em QA. Origem do objectivo e das regras operacionais: decisão expressa da Tempo; navegação, filtros e recuperação do upload são escolhas de implementação. Reutilizados biblioteca, armazenamento, perfis e histórico. Não altera os critérios de aceitação nem fecha os requisitos integrais: integração/contas reais e categorias não demonstradas conservam o seu estado. Geometrias permanecem separadas.
|
||||
|
||||
|
||||
AUD-GEO-02/03: Recuperação da demarcação territorial — QA, 22/10/2026. 408/581 limites recuperados em QA; cadeia territorial guardada/reaberta no portal. Homologacao ainda sem reposicao; consultar a nova secao no manual existente. Mantidas 173 correspondencias pendentes e ausencia de geometrias proprias das localidades. Origem: pedido expresso da Tempo de recuperar funcionalidade; enquadramento e mensagens sao escolhas tecnicas. Nao encerra o criterio de reposicao no destino.
|
||||
|
||||
|
||||
AUD-GEO-02/03 - 22/10/2026: Demarcação territorial reposta na homologação — 22/10/2026. 408 limites (11/133/264) importados pela conta real/MFA, com tres recibos auditados; backup/restauro de 171 tabelas conferido, 333 ficheiros verificados. Consulta do mapa demonstrada na oportunidade real #30, sem a alterar. Acessos previstos e originais privados preservados. Reposicao no destino concluida; 26+147 correspondencias e ausencia de poligonos proprios de localidades mantidas como lacunas reais. Ver fecho e guia no manual existente; sem repetir provas QA aceites.
|
||||
|
||||
### Incremento Concursos e anúncios — QA, 22/10/2026
|
||||
|
||||
Instalação autorizada e concluída na homologação em 22/10/2026: integração funcional `f4d09709`, schema selectivo/contrato/activação conferidos após backup e restauro isolado de 171 tabelas. Gestão real com MFA: cadastro, recuperação e prévia de rascunho privado auditado; catálogo público 200, zero anúncios publicados. Preservados dados/permissões/originais e geometrias. Evidência e links finais em «Instalação concluída na homologação» no manual e `unification/procurement-installation-receipt-20261022.json`. Provas QA aceites reutilizadas. Este fecho substitui o estado de proposta pendente de instalação abaixo, sem declarar aceite pela Tempo o guia não conferido nem expandir o âmbito para recepção de propostas.
|
||||
|
||||
Proposta de entrega da candidata `1bcf34c0`: ver «Proposta de instalação compatível com deploy automático» no manual existente. Identificados três novos cadastros e o gate real de contrato de migrations do pipeline. Sequência proposta: revisão → coordenação do deploy → backup/restauro isolado → migration selectiva autenticada com flag desligada → contrato → integração/build/promoção → activação → conferência do delta no destino. Reversão conserva tabelas, actos, auditoria e originais posteriores; não restaura backup antigo sobre dados novos. Provas QA reutilizadas; nenhuma integração, migration remota, activação ou publicação real executada nesta passagem. A Tempo ainda não conferiu links locais/guia; não registar aceitação nem entrega no destino por essa razão.
|
||||
|
||||
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.
|
||||
|
||||
@@ -76,3 +76,5 @@
|
||||
"Cópia privada ou derivado";"C:\laragon\www\portal-investidor\storage\app\private\portal-documents\5c860c48-e8cb-4d8d-ab9b-2e3ea7036ce2";"5c860c48-e8cb-4d8d-ab9b-2e3ea7036ce2";"1834284";"EA1B0DC6F5CF6E595F6D9A19E7042F398999E0087CAEF1785F6E8FAC571B5F53";"5c860c48-e8cb-4d8d-ab9b-2e3ea7036ce2";"";"Private stored copy already represented by its document record";"Acessível em armazenamento privado";"Inventariado por caminho, tamanho e SHA-256; não publicado nem aberto nesta revisão."
|
||||
"Cópia privada ou derivado";"C:\laragon\www\portal-investidor\storage\app\private\portal-documents\64c46765-96b9-4f43-afc6-f5b730c010c4";"64c46765-96b9-4f43-afc6-f5b730c010c4";"440018";"CCF2A8DEECA820F2DD076B4B2141B1EFE3F6A806568C3A587CA59A8E92F4C251";"64c46765-96b9-4f43-afc6-f5b730c010c4";"";"Private stored copy already represented by its document record";"Acessível em armazenamento privado";"Inventariado por caminho, tamanho e SHA-256; não publicado nem aberto nesta revisão."
|
||||
"Cópia privada ou derivado";"C:\laragon\www\portal-investidor\storage\app\private\portal-documents\b0ea969a-30ec-477b-8b8b-b7af82a11bfe";"b0ea969a-30ec-477b-8b8b-b7af82a11bfe";"67067";"5C69B6F189A1D3F5307E0CF7DD8224554705158A682637D3105AFAD615DB26E2";"b0ea969a-30ec-477b-8b8b-b7af82a11bfe";"C:\laragon\www\portal-investidor\storage\app\private\geo-ine-review-20260928\original.pdf + C:\laragon\www\portal-investidor\storage\app\private\geo-ine-review-20260928-v2\original.pdf + C:\laragon\www\portal-investidor\storage\app\private\portal-documents\b0ea969a-30ec-477b-8b8b-b7af82a11bfe";"Private stored copy already represented by its document record";"Acessível em armazenamento privado";"Inventariado por caminho, tamanho e SHA-256; não publicado nem aberto nesta revisão."
|
||||
"Formulario fornecido Tempo";"\\Mac\Home\Documents\TEMPO\Portal Tempo\Servicos Tempo\portal do investidor\Notas\Novo Modelo A.docx";"Novo Modelo A.docx";"532008";"62C198E90D8C6792F7D5CCEFC6386DF2E4B6089573067D3A6E1EAE7FC2B9572B";"IVA Modelo A - exemplar Tempo";"";"Original privado na biblioteca QA; sem publicacao ou activacao";"Acessivel / importado QA / privado / scan limpo";"Imagem incorporada lida visualmente; edicao/vigencia nao certificada. Correspondencia na reconciliacao Modelo A, AUD-SIM-INT02. Variante de bytes preservada, nao duplicado por nome."
|
||||
"Formulario fornecido Tempo";"\\Mac\Home\Documents\TEMPO\Portal Tempo\Servicos Tempo\portal do investidor\Notas\Novo Modelo A v1.docx";"Novo Modelo A v1.docx";"532100";"63D45FA80DB17A60B43619D55A25813229D670D14C758273F86316285DB8E02B";"IVA Modelo A - exemplar Tempo";"";"Original privado na biblioteca QA; sem publicacao ou activacao";"Acessivel / importado QA / privado / scan limpo";"Imagem incorporada lida visualmente; edicao/vigencia nao certificada. Correspondencia na reconciliacao Modelo A, AUD-SIM-INT02. Variante de bytes preservada, nao duplicado por nome."
|
||||
|
||||
|
@@ -0,0 +1,211 @@
|
||||
# Levantamento funcional, entidades e eventos — InvestMoz
|
||||
|
||||
**Data do levantamento:** 9 de Outubro de 2026
|
||||
**Âmbito:** aplicação Laravel/React presente neste repositório.
|
||||
**Base de evidência:** README, rotas, controladores, modelos, migrations e documentação em `docs/investmoz/`. A existência de uma tabela, rota ou documento não prova que a função esteja activa em produção.
|
||||
|
||||
## 1. Resumo executivo
|
||||
|
||||
O projecto é uma plataforma de investimento com área pública e área autenticada. Abrange descoberta de oportunidades e negócios, manifestações de interesse, gestão de candidaturas e carteira, CRM local, geografia e zonas de investimento, simulador e governação fiscal, catálogo documental, educação, assistência de terra, parcerias, balcão virtual, IA com fontes e integrações preparadas.
|
||||
|
||||
O README apresenta a base inicial do portal (sectores, oportunidades e manifestações). O código actual e os documentos de domínio registam a extensão funcional posterior. Para o inventário detalhado dos fluxos, consultar [casos-de-uso-aplicacao-actual.md](casos-de-uso-aplicacao-actual.md), que identifica 24 casos de uso. Este documento complementa-o com uma visão de domínio, um diagrama resumido de relações e a frequência observável dos eventos.
|
||||
|
||||
## 2. Âmbito e método
|
||||
|
||||
Foi feito inventário dos caminhos acessíveis no repositório: **981 ficheiros** por `rg --files`, excluindo dependências geradas `vendor` e `node_modules`. Dentro da aplicação foram consultados, em particular, `routes/`, `app/Models/`, `app/Http/Controllers/`, `database/migrations/`, `database/seeders/` e `docs/investmoz/`. O número é uma contagem do inventário local nessa data, não um inventário de anexos externos ou de dados de produção.
|
||||
|
||||
Classificação usada:
|
||||
|
||||
- **Implementado no código:** rota, controlador, job, comando ou schema identificável.
|
||||
- **Documentado/planeado:** descrito em documentação, sem prova suficiente de execução integrada.
|
||||
- **Frequência de negócio desconhecida:** não há amostra de eventos de produção nem telemetria para calcular volume por hora/dia/mês.
|
||||
- **Cadência técnica explícita:** periodicidade configurada no scheduler ou no mecanismo de fila.
|
||||
|
||||
## 3. Actores e sistemas
|
||||
|
||||
| Actor | Objectivo principal | Áreas usadas |
|
||||
|---|---|---|
|
||||
| Visitante | Consultar conteúdos públicos e iniciar pedidos | Portal, oportunidades, negócios, zonas, documentos, simulador |
|
||||
| Investidor/utilizador | Manter perfil, favoritos, rascunhos, submissões, documentos e mensagens | Espaço do investidor, carteira, assistência, parcerias |
|
||||
| Gestor de portal/CRM | Rever interesses, gerir leads, tarefas, catálogo e publicação | CRM local, gestão de portal |
|
||||
| Gestor territorial | Manter projectos, clusters, corredores, relações, geografia e zonas | Catálogo territorial, GIS, zonas |
|
||||
| Revisor documental | Conferir proveniência, versão, hash, autorização e análise antimalware | Biblioteca documental |
|
||||
| Gestor fiscal | Rever fontes, parâmetros e versões das regras fiscais | Governação fiscal, simulador |
|
||||
| Gestor de educação | Manter competências, necessidades, cursos, capacidades e propostas | Educação |
|
||||
| Gestor/agente de assistência | Tratar pedidos de terra e pedidos do balcão virtual | Assistência de terra, balcão |
|
||||
| Parceiro institucional | Participar em oportunidades/parcerias e colaborar em salas | Parcerias |
|
||||
| Administrador | Gerir autenticação, segurança, configurações e permissões | Administração |
|
||||
| Sistema externo/worker | Receber/enviar mensagens ou executar trabalho assíncrono | Outbox/inbox, filas, integrações |
|
||||
|
||||
Papéis são uma leitura funcional dos fluxos. A autorização efectiva depende das policies, middleware e permissões implementadas, não apenas desta tabela.
|
||||
|
||||
## 4. Catálogo de casos de uso
|
||||
|
||||
O documento [Casos de uso da aplicação actual](casos-de-uso-aplicacao-actual.md) detalha pré-condições, fluxos e dados para cada caso. Índice consolidado:
|
||||
|
||||
| ID | Caso de uso | Actor principal | Resultado |
|
||||
|---|---|---|---|
|
||||
| UC-01 | Consultar página inicial e serviços | Visitante | Entrada no serviço público autorizado |
|
||||
| UC-02 | Pesquisar sectores e oportunidades | Visitante/investidor | Lista/detalhe e possível favorito/interesse |
|
||||
| UC-03 | Consultar negócio existente | Visitante/investidor | Consulta da oferta divulgável |
|
||||
| UC-04 | Criar conta, autenticar e recuperar acesso | Utilizador | Sessão autenticada/verificada |
|
||||
| UC-05 | Manifestar interesse numa oportunidade | Visitante/investidor | Pedido recebido para tratamento |
|
||||
| UC-06 | Manifestar interesse num negócio existente | Investidor | Pedido ligado ao negócio e consentimento |
|
||||
| UC-07 | Guardar oportunidade e manter espaço do investidor | Utilizador | Favorito, dossier ou item na carteira |
|
||||
| UC-08 | Criar e manter submissão de investimento | Investidor | Submissão em rascunho com histórico |
|
||||
| UC-09 | Submeter candidatura | Investidor | Entrada em fila de tratamento |
|
||||
| UC-10 | Gerir negócio existente | Proprietário/gestor | Registo revisto e eventualmente publicável |
|
||||
| UC-11 | Tratar interesses no CRM | Gestor | Qualificação, lead, tarefa e seguimento |
|
||||
| UC-12 | Gerir catálogo territorial | Gestor territorial | Projectos, clusters, corredores e relações |
|
||||
| UC-13 | Gerir zonas de investimento | Gestor territorial/revisor | Zona validada, publicada ou suspensa |
|
||||
| UC-14 | Carregar, verificar e publicar documento | Revisor documental | Documento preservado com estados independentes |
|
||||
| UC-15 | Criar/retomar rascunho de simulação | Visitante/utilizador | Rascunho recuperável, sem cálculo implícito |
|
||||
| UC-16 | Executar simulação | Visitante/utilizador | Resultado, obrigações, fontes e relatório |
|
||||
| UC-17 | Gerir regra fiscal | Gestor fiscal | Versão de regra passa por revisão/aprovação |
|
||||
| UC-18 | Gerir educação e competências | Gestor de educação | Catálogo, análise de lacunas e proposta |
|
||||
| UC-19 | Abrir/tratar assistência de terra | Investidor/gestor | Caso acompanhado até encaminhamento/fecho |
|
||||
| UC-20 | Criar e gerir parceria | Investidor/parceiro | Interesse, sala, mensagens, documentos e marcos |
|
||||
| UC-21 | Usar balcão virtual | Utilizador/agente | Pedido de serviço acompanhado até resolução |
|
||||
| UC-22 | Usar IA com evidência | Gestor autorizado | Execução rastreável ligada a fontes e feedback |
|
||||
| UC-23 | Sincronizar sistemas externos | Worker/sistema externo | Eventos recebidos/enviados e divergências registadas |
|
||||
| UC-24 | Auditar alterações | Sistema/gestor/auditor | Histórico e correlação para operações relevantes |
|
||||
|
||||
## 5. Diagrama de entidades e relações
|
||||
|
||||
Diagrama lógico resumido por domínios. As cardinalidades representam a estrutura documentada; algumas relações polimórficas, opcionais ou em tabelas de junção estão simplificadas. Para o ERD de schema detalhado, consultar [erd.mmd](erd.mmd), [data-dictionary.csv](data-dictionary.csv) e [table-catalog.csv](table-catalog.csv).
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
USER ||--o{ INVESTOR : possui
|
||||
USER ||--o{ SUBMISSION : cria
|
||||
USER ||--o{ SIMULATION : executa
|
||||
USER ||--o{ AUDIT_EVENT : origina
|
||||
SECTOR ||--o{ OPPORTUNITY : classifica
|
||||
OPPORTUNITY ||--o{ INTEREST_REQUEST : recebe
|
||||
EXISTING_BUSINESS ||--o{ INTEREST_REQUEST : recebe
|
||||
INVESTOR ||--o{ INTEREST_REQUEST : manifesta
|
||||
INTEREST_REQUEST ||--o| LEAD : converte
|
||||
LEAD ||--o{ CRM_TASK : acompanha
|
||||
OPPORTUNITY ||--o{ SUBMISSION : referencia
|
||||
SUBMISSION ||--o{ SUBMISSION_REVISION : versiona
|
||||
SUBMISSION ||--o{ SUBMISSION_EVENT : regista
|
||||
SUBMISSION ||--o{ DOCUMENT_LINK : anexa
|
||||
PROJECT }o--o{ ECONOMIC_CLUSTER : associa
|
||||
PROJECT }o--o{ DEVELOPMENT_CORRIDOR : associa
|
||||
PROJECT ||--o{ INVESTMENT_GAP : identifica
|
||||
INVESTMENT_ZONE ||--o{ ZONE_REVISION : versiona
|
||||
INVESTMENT_ZONE ||--o{ ZONE_RELATION : liga
|
||||
INVESTMENT_ZONE ||--o{ ZONE_ATTACHMENT : documenta
|
||||
DOCUMENT ||--o{ DOCUMENT_VERSION : versiona
|
||||
DOCUMENT ||--o{ MALWARE_SCAN : analisa
|
||||
DOCUMENT }o--o{ PORTAL_RESOURCE : disponibiliza
|
||||
SIMULATION ||--o{ SIMULATION_SCENARIO : contém
|
||||
SIMULATION_SCENARIO ||--o{ SIMULATION_RESULT : produz
|
||||
SIMULATION_RESULT ||--o{ SIMULATION_OBLIGATION : discrimina
|
||||
SIMULATION_RESULT ||--o{ RESULT_SOURCE : fundamenta
|
||||
TAX_RULE_VERSION }o--o{ LEGAL_SOURCE : fundamenta
|
||||
PROJECT ||--o{ TRAINING_NEED : requer
|
||||
SKILL }o--o{ OCCUPATION : relacionada
|
||||
TRAINING_PROVIDER ||--o{ COURSE : oferece
|
||||
USER ||--o{ LAND_ASSISTANCE_CASE : requer
|
||||
PARTNERSHIP ||--o{ PARTNERSHIP_INTEREST : recebe
|
||||
PARTNERSHIP ||--o| PARTNERSHIP_ROOM : abre
|
||||
PARTNERSHIP_ROOM ||--o{ PARTNERSHIP_MESSAGE : contém
|
||||
DESK_CASE ||--o{ DESK_EVENT : regista
|
||||
AI_SOURCE ||--o{ AI_CHUNK : divide
|
||||
AI_SOURCE ||--o{ AI_RUN : suporta
|
||||
OUTBOX_EVENT }o..|| SYNC_RUN : processa
|
||||
SYNC_RUN ||--o{ RECONCILIATION_ISSUE : detecta
|
||||
```
|
||||
|
||||
### 5.1 Domínios e entidades nucleares
|
||||
|
||||
| Domínio | Entidades nucleares | Relações principais |
|
||||
|---|---|---|
|
||||
| Identidade e acesso | `users`, `passkeys`, sessões, organizações, memberships | Utilizador possui perfil e inicia operações protegidas |
|
||||
| Catálogo público | `sectors`, `opportunities`, catálogos de referência, recursos do portal | Sector classifica oportunidades; publicação controla exposição |
|
||||
| Negócios existentes | `existing_businesses`, objectos, locais, revisões, mandatos | Negócio tem objectos/locais/documentos e workflow próprio |
|
||||
| Investidor e CRM | `investors`, `interest_requests`, `leads`, `contacts`, oportunidades CRM, tarefas | Interesse pode converter-se em lead e gerar tarefas/actividades |
|
||||
| Carteira e submissões | dossiers, `investment_submissions`, necessidades, revisões, eventos, mensagens, anexos | Submissão pertence a utilizador e liga-se a oportunidade/negócio |
|
||||
| Território e GIS | `projects`, `economic_clusters`, `development_corridors`, `geo_locations`, `value_chains`, `investment_gaps` | Relações muitos-para-muitos documentam associação territorial/económica |
|
||||
| Zonas de investimento | `investment_zones`, fontes legais, geometrias, relações, anexos, revisões | Zona tem localização, evidência, relações e estado de validação/publicação |
|
||||
| Documentos | `documents`, versões, scans, associações por domínio, recursos do portal | Documento pode ser associado a várias entidades; disponibilidade, estado documental e uso em cálculo são independentes |
|
||||
| Simulação e fiscalidade | rascunhos, `simulations`, cenários, resultados, obrigações, fontes, regras versionadas | Execução usa versões/parâmetros elegíveis e preserva as fontes do resultado |
|
||||
| Educação | skills, occupations, providers, courses, training needs/capacity, propostas | Projectos expressam necessidades relacionadas com competências e formação |
|
||||
| Serviços e colaboração | casos de terra, balcão virtual, parcerias, salas, mensagens, documentos, marcos | Casos e salas mantêm eventos e participantes próprios |
|
||||
| IA e governação | controlos, fontes/chunks, execuções, feedback, auditoria | Execução de IA deve poder ser rastreada às fontes e ao actor |
|
||||
| Integrações | `outbox_events`, `inbox_events`, `sync_runs`, reconciliações | Eventos são idempotentes/correlacionáveis; desenho de schema não prova integração activa |
|
||||
|
||||
## 6. Eventos e frequência
|
||||
|
||||
### 6.1 Frequência explicitamente configurada
|
||||
|
||||
| Evento/processo | Gatilho | Frequência/cadência no código | Natureza e observação |
|
||||
|---|---|---|---|
|
||||
| Escalar tarefas vencidas (`investmoz:escalate-overdue-tasks`) | Scheduler Laravel | **A cada 15 minutos**, com `withoutOverlapping()` | Única periodicidade de scheduler encontrada em `routes/console.php`; execução real requer scheduler/cron activo |
|
||||
| Executar simulação (`RunSimulation`) | Pedido de cálculo aceite | Por submissão, via fila, `afterCommit` | Assíncrono; frequência depende da procura e de workers activos |
|
||||
| Analisar documento (`ScanDocumentForMalware`) | Upload/solicitação de scan em fluxos suportados | Por documento, via fila, `afterCommit`; job único por identidade configurada | Cadência sob procura; requer worker e scanner configurados. Upload/publicação não confirma scan limpo |
|
||||
| Comparar compatibilidade MNE (`CompareMneBusiness`) | Alteração de negócio que agenda comparação | Por alteração aplicável, em fila específica e `afterCommit` | Trabalho assíncrono/sombra; volume real desconhecido |
|
||||
| Notificar recepção de interesse (`NotifyInterestReceived`) | Disparo esperado após recepção de interesse | Job existe, mas não foi localizada chamada a `dispatch()` no código consultado; frequência de execução não confirmada | Tratar como capacidade prevista até localizar/activar o disparo; entrega efectiva também depende de configuração e worker |
|
||||
| Auditoria, revisões e eventos de domínio | Acção de utilizador/sistema | No momento da operação que os gera | Orientado a evento; não há taxa histórica neste levantamento |
|
||||
| Outbox/inbox e sincronização externa | Evento de domínio ou mensagem externa | Sem periodicidade operacional confirmada neste código/documentação consultada | Estrutura preparada; documentação aponta sincronização externa real como pendente/não activada |
|
||||
|
||||
### 6.2 Frequência por categoria de actividade
|
||||
|
||||
| Categoria | Frequência funcional esperada | O que é possível afirmar |
|
||||
|---|---|---|
|
||||
| Leituras públicas (home, catálogo, oportunidade, negócio, mapas/referências) | Sob procura do visitante | Há endpoints/páginas; sem logs ou analytics não se quantificam consultas |
|
||||
| Escritas de investidor (interesses, favoritos, submissões, mensagens, perfil) | Sob acção do utilizador | São iniciadas por pedido explícito; não há volume observado disponível |
|
||||
| Gestão (revisão, publicação, CRM, regras, zonas, catálogo) | Sob trabalho de gestores autorizados | Fluxos e protecções documentados; frequência por papel não medida |
|
||||
| Simulação e rascunhos | Sob utilização do simulador; rascunhos podem ser actualizados/retomados | Rota e jobs encontrados; não assumir execução periódica nem cálculo fiscal por guardar rascunho |
|
||||
| Uploads e análise documental | Sob envio/reanálise de documento | Job de scan é disparado por fluxos suportados; scan e publicação são estados distintos |
|
||||
| Auditoria | Sob operações auditadas | A frequência acompanha as operações instrumentadas; cobertura total precisa de confirmação por fluxo |
|
||||
| Regras fiscais e fontes | Sob processo de revisão/aprovação e alteração normativa | Não há calendário fixo; fonte publicada não activa automaticamente cálculo |
|
||||
| Integrações externas | Não determinável/activação pendente | Tabelas de integração e documentação não são prova de entrega ou sincronização real |
|
||||
|
||||
### 6.3 Frequência observada versus frequência de execução
|
||||
|
||||
Não foram disponibilizados logs, contagens de `audit_events`, filas, analytics, pedidos HTTP ou exportação da base activa. Por isso, **não é possível apresentar eventos por hora/dia/mês, sazonalidade, taxa de falhas ou utilizadores activos**. A cadência de 15 minutos é apenas o intervalo configurado para o comando agendado. Para apurar frequência real, recolher métricas autorizadas por tipo de evento, intervalo temporal e resultado, sem expor dados pessoais; documentar período da amostra, filtros, retenção e origem. Não inferir métricas a partir de seeders, testes, screenshots ou schema.
|
||||
|
||||
## 7. Sequência de alto nível
|
||||
|
||||
```text
|
||||
Visita pública
|
||||
-> pesquisa catálogo/oportunidades/negócios/território
|
||||
-> interesse, candidatura, assistência ou simulação
|
||||
-> autenticação e consentimento quando exigidos
|
||||
-> gravação do pedido/rascunho e histórico
|
||||
-> trabalho assíncrono quando aplicável (scan, cálculo, notificação)
|
||||
-> revisão por responsável autorizado
|
||||
-> decisão, comunicação ou publicação com autorização
|
||||
-> auditoria e eventual integração externa homologada
|
||||
```
|
||||
|
||||
## 8. Regras transversais que afectam os fluxos
|
||||
|
||||
- Disponibilidade para consulta, situação jurídica/documental e uso em regra/cálculo são estados independentes.
|
||||
- Importar ou publicar documento não activa regra nem cálculo fiscal.
|
||||
- Relações sem citação/evidência verificada devem ficar pendentes e identificadas.
|
||||
- Guardar um rascunho não equivale a submeter ou aprovar.
|
||||
- A divulgação obedece à autorização registada e ao seu âmbito.
|
||||
- Documento original, proveniência, identificador, edição/data, hash e histórico devem ser preservados; não marcar autenticidade, vigência, direitos ou scan como confirmados sem evidência.
|
||||
- Integração desenhada (outbox/inbox) não equivale a sincronização activa.
|
||||
- Alterações a dados activos exigem confirmação de backup/restauro e acção autenticada necessária; testes/migrações devem usar exclusivamente base QA isolada, conforme `AGENTS.md`.
|
||||
|
||||
## 9. Limitações e pontos para validação
|
||||
|
||||
1. A cobertura implementada de cada UC varia; alguns documentos descrevem trabalho parcial, proposto ou bloqueado. Consultar `docs/investmoz/validation.md`, `completion-audit-2026-10-04.md`, `delivery-gaps.md` e os README de cada domínio antes de declarar operação concluída.
|
||||
2. O ERD resumido não substitui constraints/FKs, cardinalidades exactas, escopos de acesso ou estados enumerados no schema e OpenAPI.
|
||||
3. A frequência real de utilização e dos eventos de negócio permanece **indeterminada** sem telemetria operacional.
|
||||
4. Execução do scheduler, workers, ClamAV, notificações, SuiteCRM e restantes conectores depende de configuração e operação externa verificável.
|
||||
5. O inventário desta tarefa é do repositório, não dos ficheiros/anexos em armazenamento externo nem da base de dados de produção.
|
||||
|
||||
## 10. Fontes locais principais
|
||||
|
||||
- [README do projecto](../../README.md)
|
||||
- [Casos de uso detalhados](casos-de-uso-aplicacao-actual.md)
|
||||
- [ERD do schema reconciliado](erd.mmd)
|
||||
- [Arquitectura e migrations](architecture-and-migrations.md)
|
||||
- [Validação](validation.md) e [auditoria de conclusão](completion-audit-2026-10-04.md)
|
||||
- Rotas: `routes/web.php`, `routes/api.php`, `routes/investment.php`, `routes/partnerships.php`, `routes/land-assistance.php`, `routes/investment-zones.php`, `routes/territorial-registry.php`
|
||||
- Cadência: `routes/console.php`; trabalhos assíncronos em `app/Jobs/`
|
||||
@@ -1231,5 +1231,775 @@
|
||||
"main_integrated": false,
|
||||
"homologation_rule_changed": false,
|
||||
"legal_coverage_expanded": false
|
||||
},
|
||||
"monthly_iva_projection_qa_20261021": {
|
||||
"branch": "codex/iva-monthly-projection-QA",
|
||||
"status": "candidate_verified_in_isolated_QA_only",
|
||||
"source": "existing monthly product sales; saved constant cashflow revenue only when no business plan exists",
|
||||
"basis": "explicit net/included/unknown; only net calculated; no silent conversion",
|
||||
"selection": "published version selected by server; frozen at admission; same resolver/evaluator",
|
||||
"cash_treatment": "separate IVA memorandum; declared receipts/payments unchanged; not cost, no inferred collection or final settlement",
|
||||
"manual_tax": "custom planning only; never input into monthly IVA",
|
||||
"unknown_vs_zero": "missing month indeterminate; explicit zero calculated as zero; aggregate indeterminate if any month unknown",
|
||||
"tests": {
|
||||
"api_tests": 3,
|
||||
"api_assertions": 46,
|
||||
"frontend_tests": 4,
|
||||
"types": "passed",
|
||||
"build": "passed",
|
||||
"pint": "passed",
|
||||
"accepted_calculation_and_exporter_proofs": "reused, not repeated"
|
||||
},
|
||||
"qa_fixture": {
|
||||
"database": "simulator_fiscal_delta_20261021_test",
|
||||
"rule_id": "01a15e50-7972-72e0-8991-82ca28df9b49",
|
||||
"rule_version": 4,
|
||||
"consumer_enabled": true,
|
||||
"synthetic": true,
|
||||
"left_available_for_review": true,
|
||||
"draft_id": "e0201d19-286e-4d5c-9585-2c1e3bd1ad1e"
|
||||
},
|
||||
"live_history": [
|
||||
{
|
||||
"id": "06566910-9672-4176-804d-aad8350417cb",
|
||||
"status": "completed",
|
||||
"created_at": "2026-10-21T11:57:11.000000Z",
|
||||
"basis": "net",
|
||||
"rows": [
|
||||
{
|
||||
"vat": "160.0048",
|
||||
"date": "2026-01-01",
|
||||
"rate": "16",
|
||||
"status": "calculated",
|
||||
"rule_id": "01a15e50-7972-72e0-8991-82ca28df9b49",
|
||||
"net_sales": "1000.03",
|
||||
"gross_sales": "1160.0348",
|
||||
"rule_version": 4,
|
||||
"sales_declared": "1000.03"
|
||||
},
|
||||
{
|
||||
"vat": "320.0096",
|
||||
"date": "2026-02-01",
|
||||
"rate": "16",
|
||||
"status": "calculated",
|
||||
"rule_id": "01a15e50-7972-72e0-8991-82ca28df9b49",
|
||||
"net_sales": "2000.06",
|
||||
"gross_sales": "2320.0696",
|
||||
"rule_version": 4,
|
||||
"sales_declared": "2000.06"
|
||||
},
|
||||
{
|
||||
"vat": "0",
|
||||
"date": "2026-03-01",
|
||||
"rate": "16",
|
||||
"status": "calculated",
|
||||
"rule_id": "01a15e50-7972-72e0-8991-82ca28df9b49",
|
||||
"net_sales": "0.00",
|
||||
"gross_sales": "0",
|
||||
"rule_version": 4,
|
||||
"sales_declared": "0.00"
|
||||
},
|
||||
{
|
||||
"vat": null,
|
||||
"date": "2026-04-01",
|
||||
"rate": null,
|
||||
"status": "missing_sales",
|
||||
"rule_id": "01a15e50-7972-72e0-8991-82ca28df9b49",
|
||||
"net_sales": null,
|
||||
"gross_sales": null,
|
||||
"rule_version": 4,
|
||||
"sales_declared": null
|
||||
}
|
||||
],
|
||||
"total": null,
|
||||
"reports": [
|
||||
{
|
||||
"id": "42b95158-6c4c-43c6-801d-28e973a087a6",
|
||||
"same_snapshot": true,
|
||||
"sha256": "b6d10565f82f7e83952fbdbb15a357ca3a7fee550e53c80a0ef5b9e314ccfa68"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "098900ed-2f5f-4141-98c8-08f892912a5b",
|
||||
"status": "completed",
|
||||
"created_at": "2026-10-21T11:57:34.000000Z",
|
||||
"basis": "net",
|
||||
"rows": [
|
||||
{
|
||||
"vat": "480.0144",
|
||||
"date": "2026-01-01",
|
||||
"rate": "16",
|
||||
"status": "calculated",
|
||||
"rule_id": "01a15e50-7972-72e0-8991-82ca28df9b49",
|
||||
"net_sales": "3000.09",
|
||||
"gross_sales": "3480.1044",
|
||||
"rule_version": 4,
|
||||
"sales_declared": "3000.09"
|
||||
},
|
||||
{
|
||||
"vat": "320.0096",
|
||||
"date": "2026-02-01",
|
||||
"rate": "16",
|
||||
"status": "calculated",
|
||||
"rule_id": "01a15e50-7972-72e0-8991-82ca28df9b49",
|
||||
"net_sales": "2000.06",
|
||||
"gross_sales": "2320.0696",
|
||||
"rule_version": 4,
|
||||
"sales_declared": "2000.06"
|
||||
},
|
||||
{
|
||||
"vat": "0",
|
||||
"date": "2026-03-01",
|
||||
"rate": "16",
|
||||
"status": "calculated",
|
||||
"rule_id": "01a15e50-7972-72e0-8991-82ca28df9b49",
|
||||
"net_sales": "0.00",
|
||||
"gross_sales": "0",
|
||||
"rule_version": 4,
|
||||
"sales_declared": "0.00"
|
||||
},
|
||||
{
|
||||
"vat": null,
|
||||
"date": "2026-04-01",
|
||||
"rate": null,
|
||||
"status": "missing_sales",
|
||||
"rule_id": "01a15e50-7972-72e0-8991-82ca28df9b49",
|
||||
"net_sales": null,
|
||||
"gross_sales": null,
|
||||
"rule_version": 4,
|
||||
"sales_declared": null
|
||||
}
|
||||
],
|
||||
"total": null,
|
||||
"reports": [
|
||||
{
|
||||
"id": "44211195-0a53-4f4c-93ef-6ff0e439082b",
|
||||
"same_snapshot": true,
|
||||
"sha256": "5de00868029b7a688585e180bcd8fbef1e46e289b19ab766c58f7406fb2af972"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "96eec0d8-effa-4286-b465-6af50f756db4",
|
||||
"status": "completed",
|
||||
"created_at": "2026-10-21T11:58:35.000000Z",
|
||||
"basis": "included",
|
||||
"rows": [
|
||||
{
|
||||
"vat": null,
|
||||
"date": "2026-01-01",
|
||||
"rate": null,
|
||||
"status": "unsupported_basis",
|
||||
"rule_id": "01a15e50-7972-72e0-8991-82ca28df9b49",
|
||||
"net_sales": null,
|
||||
"gross_sales": null,
|
||||
"rule_version": 4,
|
||||
"sales_declared": "3000.09"
|
||||
},
|
||||
{
|
||||
"vat": null,
|
||||
"date": "2026-02-01",
|
||||
"rate": null,
|
||||
"status": "unsupported_basis",
|
||||
"rule_id": "01a15e50-7972-72e0-8991-82ca28df9b49",
|
||||
"net_sales": null,
|
||||
"gross_sales": null,
|
||||
"rule_version": 4,
|
||||
"sales_declared": "2000.06"
|
||||
},
|
||||
{
|
||||
"vat": null,
|
||||
"date": "2026-03-01",
|
||||
"rate": null,
|
||||
"status": "unsupported_basis",
|
||||
"rule_id": "01a15e50-7972-72e0-8991-82ca28df9b49",
|
||||
"net_sales": null,
|
||||
"gross_sales": null,
|
||||
"rule_version": 4,
|
||||
"sales_declared": "0.00"
|
||||
},
|
||||
{
|
||||
"vat": null,
|
||||
"date": "2026-04-01",
|
||||
"rate": null,
|
||||
"status": "missing_sales",
|
||||
"rule_id": "01a15e50-7972-72e0-8991-82ca28df9b49",
|
||||
"net_sales": null,
|
||||
"gross_sales": null,
|
||||
"rule_version": 4,
|
||||
"sales_declared": null
|
||||
}
|
||||
],
|
||||
"total": null,
|
||||
"reports": [
|
||||
{
|
||||
"id": "821e6347-680c-4029-9756-c3169d79f85e",
|
||||
"same_snapshot": true,
|
||||
"sha256": "06abadc593e65cfff64d208fac6cac3a8f98db4bef92795a27ce637fe7958cec"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "e88aaaea-4482-4b7d-869f-f0648a430651",
|
||||
"status": "completed",
|
||||
"created_at": "2026-10-21T12:00:32.000000Z",
|
||||
"basis": "net",
|
||||
"rows": [
|
||||
{
|
||||
"vat": "480.0144",
|
||||
"date": "2026-01-01",
|
||||
"rate": "16",
|
||||
"status": "calculated",
|
||||
"rule_id": "01a15e50-7972-72e0-8991-82ca28df9b49",
|
||||
"net_sales": "3000.09",
|
||||
"gross_sales": "3480.1044",
|
||||
"rule_version": 4,
|
||||
"sales_declared": "3000.09"
|
||||
},
|
||||
{
|
||||
"vat": "320.0096",
|
||||
"date": "2026-02-01",
|
||||
"rate": "16",
|
||||
"status": "calculated",
|
||||
"rule_id": "01a15e50-7972-72e0-8991-82ca28df9b49",
|
||||
"net_sales": "2000.06",
|
||||
"gross_sales": "2320.0696",
|
||||
"rule_version": 4,
|
||||
"sales_declared": "2000.06"
|
||||
},
|
||||
{
|
||||
"vat": "0",
|
||||
"date": "2026-03-01",
|
||||
"rate": "16",
|
||||
"status": "calculated",
|
||||
"rule_id": "01a15e50-7972-72e0-8991-82ca28df9b49",
|
||||
"net_sales": "0.00",
|
||||
"gross_sales": "0",
|
||||
"rule_version": 4,
|
||||
"sales_declared": "0.00"
|
||||
},
|
||||
{
|
||||
"vat": null,
|
||||
"date": "2026-04-01",
|
||||
"rate": null,
|
||||
"status": "missing_sales",
|
||||
"rule_id": "01a15e50-7972-72e0-8991-82ca28df9b49",
|
||||
"net_sales": null,
|
||||
"gross_sales": null,
|
||||
"rule_version": 4,
|
||||
"sales_declared": null
|
||||
}
|
||||
],
|
||||
"total": null,
|
||||
"reports": [
|
||||
{
|
||||
"id": "bc204cc7-1f0c-4468-80fa-e093642a5eac",
|
||||
"same_snapshot": true,
|
||||
"sha256": "4ed224f7de50b533f144f7f411d240eb1ccca6f3e5bff7a64b1eca6f6d7971dc"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"main_integrated": false,
|
||||
"homologation_changed": false,
|
||||
"rule_publication_on_destination": false,
|
||||
"coverage_expanded": false,
|
||||
"limitations": [
|
||||
"IVA included prices unsupported in current net lot",
|
||||
"2026 only, max 12 consecutive months",
|
||||
"no final payable, deductions, refunds, special/reduced/exempt regimes",
|
||||
"examples prove arithmetic/integration, not legal eligibility of every transaction"
|
||||
],
|
||||
"remaining": "Tempo review/decision for code integration; no further generic preparation identified; keep real rule/publication/activation separate",
|
||||
"proof_directory": "outputs/simulator-continuation-20261020"
|
||||
},
|
||||
"modelo_a_reconciliation_qa_20261021": {
|
||||
"branch": "codex/iva-modelo-a-reconciliation-QA",
|
||||
"parent_candidate": "3f5430c3",
|
||||
"status": "documentary_and_first_interface_increment_verified; full IVA settlement remains incomplete",
|
||||
"reference": "docs/investmoz/simulations/modelo-a-reconciliacao-20261021.md",
|
||||
"originals": [
|
||||
{
|
||||
"id": "fbfa820d-29d5-4bf8-9f81-534931d80cef",
|
||||
"file_name": "Novo Modelo A.docx",
|
||||
"sha256": "62c198e90d8c6792f7d5ccefc6386df2e4b6089573067d3a6e1eae7fc2b9572b",
|
||||
"status": "active",
|
||||
"malware_scan_status": "clean",
|
||||
"malware_scanned_sha256": "62c198e90d8c6792f7d5ccefc6386df2e4b6089573067d3a6e1eae7fc2b9572b"
|
||||
},
|
||||
{
|
||||
"id": "cf8b799e-44de-4497-b2e3-a5c468f2fe0e",
|
||||
"file_name": "Novo Modelo A v1.docx",
|
||||
"sha256": "63d45fa80db17a60b43619d55a25813229d670d14c758273f86316285db8e02b",
|
||||
"status": "active",
|
||||
"malware_scan_status": "clean",
|
||||
"malware_scanned_sha256": "63d45fa80db17a60b43619d55a25813229d670d14c758273f86316285db8e02b"
|
||||
}
|
||||
],
|
||||
"library_qa_only": true,
|
||||
"resources": [
|
||||
33,
|
||||
34
|
||||
],
|
||||
"published": false,
|
||||
"inventory": "Existing document corpus inventory extended; private inventory of all seven accessible Notes files preserved",
|
||||
"read": "OOXML text plus embedded image2.png visually inspected; image identical in both originals; source unchanged; all three LibreOffice reading pages inspected, field25 confirmed visually; conversion layout drift is not a replacement original",
|
||||
"coverage": {
|
||||
"fields_mapped": "01–27",
|
||||
"frames_mapped": "1–13 plus ambiguously numbered prior-credit block",
|
||||
"calculated": "Existing net general-rate component01/02 only",
|
||||
"totals14_to18": "indeterminate, not inferred from VAT on sales"
|
||||
},
|
||||
"implemented": [
|
||||
"Consultable field/purpose/data/calculation/basis/status table in normal QA simulator",
|
||||
"Existing purchase schedules reused independently of sales completeness",
|
||||
"Net/supported VAT/gross/deductible VAT kept separate and unknown without classification",
|
||||
"Partial simulation marker in report; historical server snapshots not rewritten"
|
||||
],
|
||||
"documentary_discrepancies": [
|
||||
"Regularization prose11/12 versus boxes12/13",
|
||||
"Credit note18/19 versus block19/20",
|
||||
"Refund noteart20 versus art21 amended in2025",
|
||||
"Ambiguous frame8 remissions; repeated25 was extraction artifact, one box25 confirmed visually"
|
||||
],
|
||||
"official_sources_read": [
|
||||
"Existing Lei22/2022 PDF4 art17-A and entry into force",
|
||||
"Existing Lei10/2025 BR248 PDF12–15; visualPDF13/14"
|
||||
],
|
||||
"unresolved": [
|
||||
"Exact approved edition of form for period",
|
||||
"Consolidation of relevant base provisions, Lei3/2025 and regulations for each extension",
|
||||
"Operation-level scopes, eligibility, documents and effective dates for reduced rate/deduction"
|
||||
],
|
||||
"tool_limits": [
|
||||
"Direct AT form page timed out; indexed page has different frame numbering, not used as replacement",
|
||||
"Current chat attachment was not exposed as a local attachment; exact-named local Tempo Notes exemplar used with hash disclosed"
|
||||
],
|
||||
"tests": {
|
||||
"new_frontend_tests": 3,
|
||||
"types": "passed",
|
||||
"build": "passed",
|
||||
"browser": "new correspondence/purchases/report annotations only, no new calculation",
|
||||
"import": "preview/private import/clean hash-matched scan/idempotent replay with zero duplicates"
|
||||
},
|
||||
"reused_proofs": [
|
||||
"published rate selection",
|
||||
"decimal calculation",
|
||||
"monthly recovery",
|
||||
"idempotency/suspension/history",
|
||||
"CSV/DOCX/PDF exporters"
|
||||
],
|
||||
"next_lot_estimate": {
|
||||
"cadastre_and_consolidation_hours": [
|
||||
8,
|
||||
16
|
||||
],
|
||||
"code_and_acceptance_hours": [
|
||||
20,
|
||||
36
|
||||
],
|
||||
"total_hours": [
|
||||
28,
|
||||
52
|
||||
],
|
||||
"excludes": "prior credits/substitutions/payment/refund and full fiscal coverage"
|
||||
},
|
||||
"main_integrated": false,
|
||||
"homologation_changed": false,
|
||||
"new_rules_registered": false,
|
||||
"new_rules_published": false,
|
||||
"consumer_activation_changed": false,
|
||||
"proof_directory": "outputs/modelo-a-20261021"
|
||||
},
|
||||
"contextual_continuity_qa_20261022": {
|
||||
"branch": "codex/project-context-continuity-QA",
|
||||
"base": "57fc914821d0cefa02caa80a74b3d42e663f2742",
|
||||
"scope": "Primeiro incremento de continuidade e explicação contextual; adaptação jurídica integral não encerrada",
|
||||
"status": "QA, revisão; sem integração main ou alteração fiscal activa",
|
||||
"http_tests": {
|
||||
"tests": 4,
|
||||
"assertions": 74,
|
||||
"transaction_rollback": true
|
||||
},
|
||||
"frontend_tests": 8,
|
||||
"typescript": "passed",
|
||||
"build": "passed",
|
||||
"visual_evidence_directory": "outputs/context-continuity-20261022",
|
||||
"same_project_new_session": true,
|
||||
"deliberate_edit_preserved": true,
|
||||
"second_project_isolated": true,
|
||||
"fiscal_execution_id": "ae401465-834f-4761-9406-5af060c499fb",
|
||||
"new_executions_today": 1,
|
||||
"reused_proofs": [
|
||||
"13f68f1b: recuperação/exportadores/PDF",
|
||||
"83d2f97d e 226fd308: precisão/versões/históricos",
|
||||
"3f5430c3: cálculo mensal",
|
||||
"0b0cf761: correspondência parcial Modelo A"
|
||||
],
|
||||
"limitations": [
|
||||
"Catálogo visual QA sintético; ZEE/ZFI apenas controlos HTTP sintéticos revertidos, sem elegibilidade real demonstrada",
|
||||
"Condições jurídicas por sector/actividade/regime e benefícios executáveis não concluídos",
|
||||
"Outros três consumidores usam origem/reconciliador comum; não houve demonstração visual integral de cada processo",
|
||||
"Sem regra fiscal nova, publicação, activação ou integração em main",
|
||||
"Edge interrompeu a inspecção após navegação de regresso; endereço e gravação confirmados por inventário/serviço, visual final não observada. Limitação da ferramenta."
|
||||
],
|
||||
"return_evidence_id": "43317c22-50c8-40cf-98e4-e5d4d108c372",
|
||||
"B_recovered_context_revision": 2
|
||||
},
|
||||
"context_review_and_adaptive_path_preparation_20261022": {
|
||||
"reviewed_commit": "a76369eb",
|
||||
"review_status": "complete within accepted increment scope; no integration executed",
|
||||
"last_return_equivalent_evidence": "43317c22-50c8-40cf-98e4-e5d4d108c372",
|
||||
"accepted_AB_repeated": false,
|
||||
"next_lot": "Preparação de pedido de investimento privado: condição, fonte/período, formulário, percurso, cálculo e lacuna",
|
||||
"next_lot_status": "documented, not implemented or demonstrated end-to-end",
|
||||
"source_url": "https://pt.apiex.gov.mz/wp-content/uploads/2025/02/LEGISLACAO-DE-INVESTIMENTOS-EM-MOCAMBIQUE.pdf",
|
||||
"source_sha256": "ba1e9eaabfec5d0c4cb2d9f2444a6860f7a90f222034764197829be402d408ed",
|
||||
"new_fiscal_rule_activation": false,
|
||||
"next_acceptance": "Only synthetic C condition transitions and dossier/report links; reuse accepted context and IVA evidence"
|
||||
},
|
||||
"private_investment_executable_20261022": {
|
||||
"base": "52fbb146",
|
||||
"branch": "codex/private-investment-path-executable-QA",
|
||||
"status": "implemented and verified in isolated QA; not integrated",
|
||||
"source_version": "private-investment-preparation-2026-v1",
|
||||
"orientation": "Tempo 22/10: official sources execute without generic additional legal approval; permanent AGENTS updated",
|
||||
"qa_url": "http://127.0.0.1:18580/investimentos/4133311b-5426-4cec-900d-1f1484484083",
|
||||
"persistence": {
|
||||
"case_id": "4133311b-5426-4cec-900d-1f1484484083",
|
||||
"case_version": 8,
|
||||
"context_revision": 4,
|
||||
"mode": "registration",
|
||||
"association": {
|
||||
"lease": "e5ce142a-1178-46a2-b039-f187139c62cd"
|
||||
},
|
||||
"documents": 1,
|
||||
"context_events": 4,
|
||||
"draft_id": "beba1dec-fc05-4820-b099-24952c5ab36c",
|
||||
"draft_version": 4,
|
||||
"draft_sha256": "4136e82a3b9420ab8f980ec0d7bda6dc2a67b568c18535b5ba0ecc80efd37e37",
|
||||
"saved_context_revision": 3,
|
||||
"saved_preparation_mode": "authorization",
|
||||
"currency": "MZN",
|
||||
"date": "2026-01-01",
|
||||
"preparation_version": "private-investment-preparation-2026-v1"
|
||||
},
|
||||
"new_checks": {
|
||||
"feature_tests": 3,
|
||||
"assertions": 96,
|
||||
"frontend_delta_tests": 1,
|
||||
"typescript": "passed",
|
||||
"build": "passed"
|
||||
},
|
||||
"evidence_sha256": {
|
||||
"C-authorization-preview.png": "311c9f14ac601429666ded55c5906aa8ab018b5d31a1f101b4fb32697adf0817",
|
||||
"C-final-directness-preview.png": "2037958e2879fb184cac1c4aa07ddc367417dbd1541d979c4e653a63918cdaca",
|
||||
"C-final-path.png": "0c3c0900d861724b6def3f5850bd6c6c0f65dfee7cb01d6bb7c72c8bf645645a",
|
||||
"C-persistence-v3.json": "b8faad9a5d4d9174a489a7b1096104f3a91645201b221b1ad7ece4a217180e00",
|
||||
"C-persistence.json": "6b6120c88f41cff43b185905fdfec992ca76b359d0eab6bc1df02dee064c780d",
|
||||
"C-recovered-path.png": "c734d119b0f766d8600892713be473d1c1875cb6ff4474b6fceabef702a3490b",
|
||||
"C-report-check.json": "217b56b89041e6c8c05907af48c48dadf4f8316a2cce0f9195daf828258cb503",
|
||||
"C-report.docx": "28cfe4b75116c892d3e29fde389de7d067b456761756bab732af625178ee69ce"
|
||||
},
|
||||
"report": {
|
||||
"path": "C:\\Mac\\Home\\Downloads\\relatorio-investmoz-sintetico-qa-c-pedido-de-investimento-privado.docx",
|
||||
"size": 16674,
|
||||
"sha256": "28cfe4b75116c892d3e29fde389de7d067b456761756bab732af625178ee69ce"
|
||||
},
|
||||
"tool_limitation": "Download observation timed out and browser debugger detached; completed DOCX found in Downloads, verified before any retry; no repeated export",
|
||||
"reused": "Accepted A/B, decimal calculation, CSV, exporters and native PDF pagination",
|
||||
"main_integrated": false,
|
||||
"homologation_changed": false,
|
||||
"fiscal_rule_published_or_activated": false,
|
||||
"limitations": [
|
||||
"Only 2026 private investment preparation, documented registration/authorization conditions",
|
||||
"No administrative submission or entitlement decision",
|
||||
"No full sector licensing, foreign-currency equivalence or fiscal benefits engine",
|
||||
"ZEE/ZFI routing predicate uses QA references; no real zone visual recognition proof",
|
||||
"Dossier attachment association does not approve content; synthetic C fixture pending scan not delivered as seed",
|
||||
"Final C context v4 registration differs from frozen draft/report context v3 authorization; no silent rewrite or repeated export."
|
||||
],
|
||||
"historical_report_persistence": {
|
||||
"case_id": "4133311b-5426-4cec-900d-1f1484484083",
|
||||
"case_version": 7,
|
||||
"context_revision": 3,
|
||||
"mode": "authorization",
|
||||
"association": {
|
||||
"lease": "e5ce142a-1178-46a2-b039-f187139c62cd"
|
||||
},
|
||||
"documents": 1,
|
||||
"context_events": 3,
|
||||
"draft_id": "beba1dec-fc05-4820-b099-24952c5ab36c",
|
||||
"draft_version": 4,
|
||||
"draft_sha256": "4136e82a3b9420ab8f980ec0d7bda6dc2a67b568c18535b5ba0ecc80efd37e37",
|
||||
"saved_context_revision": 3,
|
||||
"saved_preparation_mode": "authorization",
|
||||
"currency": "MZN",
|
||||
"date": "2026-01-01",
|
||||
"preparation_version": "private-investment-preparation-2026-v1"
|
||||
},
|
||||
"directness_control": "Lei 8/2023 art14(b), R13: direct declared before using direct-investment monetary threshold; indirect/unknown unresolved only for dependent results; PPP independent condition remains executable"
|
||||
},
|
||||
"private_investment_report_alignment_20261022": {
|
||||
"functional_revision": "a1fbda06",
|
||||
"status": "targeted missing evidence completed; no functional code changes",
|
||||
"current_persistence": {
|
||||
"case_id": "4133311b-5426-4cec-900d-1f1484484083",
|
||||
"case_version": 8,
|
||||
"context_revision": 4,
|
||||
"mode": "registration",
|
||||
"association": {
|
||||
"lease": "e5ce142a-1178-46a2-b039-f187139c62cd"
|
||||
},
|
||||
"documents": 1,
|
||||
"context_events": 4,
|
||||
"draft_id": "beba1dec-fc05-4820-b099-24952c5ab36c",
|
||||
"draft_version": 5,
|
||||
"draft_sha256": "ba824fe0362ca87b301da55298122796f44fdfb11507830b3fe8f54cf88d132d",
|
||||
"saved_context_revision": 4,
|
||||
"saved_preparation_mode": "registration",
|
||||
"currency": "MZN",
|
||||
"date": "2026-01-01",
|
||||
"preparation_version": "private-investment-preparation-2026-v1"
|
||||
},
|
||||
"reports": {
|
||||
"historic_v3": {
|
||||
"path": "C:\\Mac\\Home\\Downloads\\relatorio-investmoz-sintetico-qa-c-pedido-de-investimento-privado.docx",
|
||||
"sha256": "28cfe4b75116c892d3e29fde389de7d067b456761756bab732af625178ee69ce",
|
||||
"bytes": 16674,
|
||||
"rows": [
|
||||
[
|
||||
"Contexto do investimento",
|
||||
"Revisão 3; sem ZEE, ZFI ou parque seleccionado",
|
||||
"Contexto versionado; não é reconhecimento jurídico. Referência do mesmo dossier no momento da gravação"
|
||||
],
|
||||
[
|
||||
"Preparação do investimento privado",
|
||||
"Pedido de autorização",
|
||||
"Declarações e requisitos de preparação; sem aprovação jurídica. Contexto congelado v3; private-investment-preparation-2026-v1"
|
||||
],
|
||||
[
|
||||
"Estudo de viabilidade técnica e económico-financeira",
|
||||
"Documento por associar",
|
||||
"Preparação informativa — simulação, sem submissão à Administração. Integra a preparação do pedido de autorização. R14(1)–(2); https://pt.apiex.gov.mz/wp-content/uploads/2025/02/LEGISLACAO-DE-INVESTIMENTOS-EM-MOCAMBIQUE.pdf#page=45"
|
||||
],
|
||||
[
|
||||
"Comprovação da capacidade financeira",
|
||||
"Documento por associar",
|
||||
"Preparação informativa — simulação, sem submissão à Administração. Integra a preparação do pedido de autorização. R14(1)–(2); https://pt.apiex.gov.mz/wp-content/uploads/2025/02/LEGISLACAO-DE-INVESTIMENTOS-EM-MOCAMBIQUE.pdf#page=45"
|
||||
]
|
||||
]
|
||||
},
|
||||
"current_v4": {
|
||||
"path": "C:\\Mac\\Home\\Downloads\\relatorio-investmoz-sintetico-qa-c-pedido-de-investimento-privado (1).docx",
|
||||
"sha256": "a2849427f4a91fa6ab78e433be8b919baca80a0f4f452a4546a5b169b921d753",
|
||||
"bytes": 16678,
|
||||
"rows": [
|
||||
[
|
||||
"Contexto do investimento",
|
||||
"Revisão 4; sem ZEE, ZFI ou parque seleccionado",
|
||||
"Contexto versionado; não é reconhecimento jurídico. Referência do mesmo dossier no momento da gravação"
|
||||
],
|
||||
[
|
||||
"Preparação do investimento privado",
|
||||
"Pedido de mero registo",
|
||||
"Declarações e requisitos de preparação; sem aprovação jurídica. Contexto congelado v4; private-investment-preparation-2026-v1"
|
||||
],
|
||||
[
|
||||
"Estudo de viabilidade técnica e económico-financeira",
|
||||
"Não aplicável; histórico preservado",
|
||||
"Preparação informativa — simulação, sem submissão à Administração. Integra a preparação do pedido de autorização. R14(1)–(2); https://pt.apiex.gov.mz/wp-content/uploads/2025/02/LEGISLACAO-DE-INVESTIMENTOS-EM-MOCAMBIQUE.pdf#page=45"
|
||||
],
|
||||
[
|
||||
"Comprovação da capacidade financeira",
|
||||
"Não aplicável; histórico preservado",
|
||||
"Preparação informativa — simulação, sem submissão à Administração. Integra a preparação do pedido de autorização. R14(1)–(2); https://pt.apiex.gov.mz/wp-content/uploads/2025/02/LEGISLACAO-DE-INVESTIMENTOS-EM-MOCAMBIQUE.pdf#page=45"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"proof_sha256": {
|
||||
"C-report-HISTORICO-contexto-v3.docx": "28cfe4b75116c892d3e29fde389de7d067b456761756bab732af625178ee69ce",
|
||||
"C-report-contexto-v4.docx": "a2849427f4a91fa6ab78e433be8b919baca80a0f4f452a4546a5b169b921d753",
|
||||
"C-report-v3-v4-comparison.json": "722154fd38b2915ec087f804abe66bc4c98c6eff2c4403d8e670d65d257e3446",
|
||||
"C-persistence-v4-draft5.json": "a10505a52c553b4c0e03ece59d20d5a0a20204ae694bcf7174ee0574272ec1c4",
|
||||
"C-new-report-context-v4.png": "b0cf159e6be5a457efdc46eb5e273895c43010caa9727e3b75668f916aa11f8d"
|
||||
},
|
||||
"historic_label": "C-report-HISTORICO-contexto-v3.docx; original bytes unchanged, embedded Contexto congelado v3",
|
||||
"current_label": "C-report-contexto-v4.docx; embedded Contexto congelado v4; registration; study/capacity not applicable",
|
||||
"accepted_tests_repeated": false,
|
||||
"integration_dependencies": [
|
||||
"a76369eb common continuity, required ancestor of a1fbda06",
|
||||
"52fbb146 documentary intermediate, no executable delta",
|
||||
"a1fbda06 adaptive private-investment functionality",
|
||||
"Current branch includes ancestors; do not cherry-pick a1fbda06 alone without a76369eb/equivalent changes"
|
||||
],
|
||||
"integration_procedure": "After Tempo evaluation/decision, fetch current main, merge preserving concurrent changes and review actual conflicts. a76369eb can integrate independently first; shared ancestry avoids duplicate application. No reset or force-push main. Confirm deployed revision and only changed destination behavior. PRIVATE_INVESTMENT_PREPARATION_ENABLED is separate from tax publication/activation.",
|
||||
"remote_main_read": "57fc914821d0cefa02caa80a74b3d42e663f2742",
|
||||
"main_integrated": false,
|
||||
"fiscal_rules_changed": false
|
||||
},
|
||||
"adaptive_private_path_homologation_20261022": {
|
||||
"authorized_by": "Tempo, user authorization 22/10/2026",
|
||||
"previous_main": "57fc914821d0cefa02caa80a74b3d42e663f2742",
|
||||
"authorized_ancestry": [
|
||||
"a76369eb",
|
||||
"52fbb146",
|
||||
"a1fbda06",
|
||||
"064296e1"
|
||||
],
|
||||
"integration": "fast-forward, no conflicts or concurrent commits observed at push; no forced update",
|
||||
"first_attempt": {
|
||||
"status": "failed",
|
||||
"commit": "064296e1fbf7b17f932df23c7b3ee5508719dec5",
|
||||
"error": "Verification command failed: node (see protected build log)",
|
||||
"active_release_preserved": true,
|
||||
"requested_commits": [
|
||||
"064296e1fbf7b17f932df23c7b3ee5508719dec5"
|
||||
],
|
||||
"finished_at": "2026-10-22T08:11:54.420414+00:00"
|
||||
},
|
||||
"compatibility_fix": {
|
||||
"commit": "042ffbde44cac27704d6f8b7ec25b525a42e3575",
|
||||
"cause": "Empty inheritance metadata appended when restoring a legacy draft with no compatible source",
|
||||
"change": "Return original draft when both compatible source and prior inheritance metadata are absent",
|
||||
"targeted_tests": "9 inheritance controls + exact failing legacy partition/recovery test, all passed"
|
||||
},
|
||||
"installed": {
|
||||
"status": "installed",
|
||||
"commit": "042ffbde44cac27704d6f8b7ec25b525a42e3575",
|
||||
"release": "/var/www/investmoz-homologacao/releases/042ffbde44cac27704d6f8b7ec25b525a42e3575-6dfae60c",
|
||||
"previous_release": "/var/www/investmoz-homologacao/releases/57fc914821d0cefa02caa80a74b3d42e663f2742-f563f9a7",
|
||||
"database_migrations": 0,
|
||||
"content_imports": 0,
|
||||
"requested_commits": [
|
||||
"042ffbde44cac27704d6f8b7ec25b525a42e3575"
|
||||
],
|
||||
"finished_at": "2026-10-22T08:16:16.903501+00:00"
|
||||
},
|
||||
"http": {
|
||||
"/build/revision.json": {
|
||||
"status": 200,
|
||||
"body": {
|
||||
"commit": "042ffbde44cac27704d6f8b7ec25b525a42e3575",
|
||||
"branch": "main",
|
||||
"built_at": "2026-10-22T08:16:14.331119+00:00",
|
||||
"manifest_sha256": "655eef6e6a5e71c5138c68465a5b2ffc36d2cadeff96e6b35682dbfd19d42e79"
|
||||
}
|
||||
},
|
||||
"/api/v1/fiscal-consumer": {
|
||||
"status": 200,
|
||||
"body": {
|
||||
"data": {
|
||||
"enabled": true,
|
||||
"coverage_start": "2026-01-01",
|
||||
"coverage_end": "2026-12-31",
|
||||
"message": "IVA geral sobre base líquida disponível; enquadramento declarado."
|
||||
}
|
||||
}
|
||||
},
|
||||
"/investimentos": {
|
||||
"status": 302,
|
||||
"location": "https://teste-investidor.tempo.co.mz/login"
|
||||
},
|
||||
"/simulador-fiscal": {
|
||||
"status": 200,
|
||||
"body": "HTML available"
|
||||
},
|
||||
"checked_utc": "2026-10-22T08:17:20.250804+00:00"
|
||||
},
|
||||
"configuration_before": {
|
||||
"read_only": true,
|
||||
"utc": "2026-10-22T08:16:34.265947+00:00",
|
||||
"revision": {
|
||||
"commit": "042ffbde44cac27704d6f8b7ec25b525a42e3575",
|
||||
"branch": "main",
|
||||
"built_at": "2026-10-22T08:16:14.331119+00:00",
|
||||
"manifest_sha256": "655eef6e6a5e71c5138c68465a5b2ffc36d2cadeff96e6b35682dbfd19d42e79"
|
||||
},
|
||||
"live": "/var/www/investmoz-homologacao/releases/042ffbde44cac27704d6f8b7ec25b525a42e3575-6dfae60c",
|
||||
"env_path": "/var/www/html/teste-investidor.tempo.co.mz/.env",
|
||||
"env_sha256": "7cdaba591bb5787485a349c24db2fd7c8279436da0eb31502e44db2fffa21d03",
|
||||
"env_mode": "0o640",
|
||||
"uid": 1001,
|
||||
"gid": 33,
|
||||
"private_preparation_env": "unset",
|
||||
"config": {
|
||||
"private_preparation": false,
|
||||
"investor_path": true,
|
||||
"fiscal_consumer": {
|
||||
"enabled": true,
|
||||
"coverage_start": "2026-01-01",
|
||||
"coverage_end": "2026-12-31",
|
||||
"message": "IVA geral sobre base líquida disponível; enquadramento declarado."
|
||||
},
|
||||
"preparation_version": "private-investment-preparation-2026-v1"
|
||||
}
|
||||
},
|
||||
"configuration_enable": {
|
||||
"utc": "2026-10-22T08:16:49.343216+00:00",
|
||||
"revision": {
|
||||
"commit": "042ffbde44cac27704d6f8b7ec25b525a42e3575",
|
||||
"branch": "main",
|
||||
"built_at": "2026-10-22T08:16:14.331119+00:00",
|
||||
"manifest_sha256": "655eef6e6a5e71c5138c68465a5b2ffc36d2cadeff96e6b35682dbfd19d42e79"
|
||||
},
|
||||
"changed": true,
|
||||
"env_before_sha256": "7cdaba591bb5787485a349c24db2fd7c8279436da0eb31502e44db2fffa21d03",
|
||||
"env_after_sha256": "a6b4e9064cf6dbb2d945b53418a6c9252f9c736ff3fde4016b95b74885a9ee0f",
|
||||
"backup": "/var/lib/investmoz-deploy/backups/private-preparation-20261022/env-7cdaba591bb5787485a349c24db2fd7c8279436da0eb31502e44db2fffa21d03.private",
|
||||
"backup_bytes_verified": true,
|
||||
"only_key_changed": "PRIVATE_INVESTMENT_PREPARATION_ENABLED",
|
||||
"mode_preserved": true,
|
||||
"owner_preserved": true,
|
||||
"config": {
|
||||
"private_preparation": true,
|
||||
"investor_path": true,
|
||||
"fiscal_consumer": {
|
||||
"enabled": true,
|
||||
"coverage_start": "2026-01-01",
|
||||
"coverage_end": "2026-12-31",
|
||||
"message": "IVA geral sobre base líquida disponível; enquadramento declarado."
|
||||
},
|
||||
"preparation_version": "private-investment-preparation-2026-v1"
|
||||
},
|
||||
"fiscal_configuration_changed": false,
|
||||
"database_operations": false,
|
||||
"recovery": "Restore previous key value only against recorded post-change hash, rebuild config cache; preserve subsequent changes and all data"
|
||||
},
|
||||
"configuration_after": {
|
||||
"read_only": true,
|
||||
"utc": "2026-10-22T08:17:21.883131+00:00",
|
||||
"revision": {
|
||||
"commit": "042ffbde44cac27704d6f8b7ec25b525a42e3575",
|
||||
"branch": "main",
|
||||
"built_at": "2026-10-22T08:16:14.331119+00:00",
|
||||
"manifest_sha256": "655eef6e6a5e71c5138c68465a5b2ffc36d2cadeff96e6b35682dbfd19d42e79"
|
||||
},
|
||||
"live": "/var/www/investmoz-homologacao/releases/042ffbde44cac27704d6f8b7ec25b525a42e3575-6dfae60c",
|
||||
"env_path": "/var/www/html/teste-investidor.tempo.co.mz/.env",
|
||||
"env_sha256": "a6b4e9064cf6dbb2d945b53418a6c9252f9c736ff3fde4016b95b74885a9ee0f",
|
||||
"env_mode": "0o640",
|
||||
"uid": 1001,
|
||||
"gid": 33,
|
||||
"private_preparation_env": "true",
|
||||
"config": {
|
||||
"private_preparation": true,
|
||||
"investor_path": true,
|
||||
"fiscal_consumer": {
|
||||
"enabled": true,
|
||||
"coverage_start": "2026-01-01",
|
||||
"coverage_end": "2026-12-31",
|
||||
"message": "IVA geral sobre base líquida disponível; enquadramento declarado."
|
||||
},
|
||||
"preparation_version": "private-investment-preparation-2026-v1"
|
||||
}
|
||||
},
|
||||
"effective_procedural_version": "private-investment-preparation-2026-v1",
|
||||
"new_fiscal_rules_published_or_activated": false,
|
||||
"qa_fixtures_copied": false,
|
||||
"accepted_scenarios_repeated_manually": false,
|
||||
"limitations": "No new scenario executed in homologation and no real dossier or personal data created. QA functional evidence reused; destination verification limited to revision, configuration and entry routes. Sector/fiscal exclusions remain explicit."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
# Reconciliação do IVA com o Novo Modelo A
|
||||
|
||||
21/10/2026. Referência AUD-SIM-INT02, sem nova matriz/backlog. Preparação progressiva em QA; não é apuramento completo, submissão ou aceitação pela AT. A componente16% existente continua independente das restantes componentes por cadastrar/desenvolver. A leitura e aceitação documental da Tempo não é apresentada como validação jurídica independente.
|
||||
|
||||
## Exemplar e preservação
|
||||
|
||||
Exemplar exacto: **Novo Modelo A.docx**, 532008bytes, SHA256 **62c198e90d8c6792f7d5ccefc6386df2e4b6089573067d3a6e1eae7fc2b9572b**. Localizado na pasta de notas do portal indicada no inventário privado desta execução, não substituído por formulário genérico ou pela página da AT. O quadro1–8 é imagem incorporada: word/media/image2.png, SHA2567ae7f02efca96323087d0dfa5f3078b4ae724318edd5b6c4802c863d840bdf66, conferida visualmente sobre fundo branco para tornar legível a transparência. Os blocos seguintes e respectivas instruções foram extraídos do OOXML. Também se conferiram visualmente as três páginas da conversão de leitura em LibreOffice: quadros1–8 na primeira, créditos/quadros9–13 na segunda e apenas o rodapé de distribuição na terceira. A conversão apresenta deslocações de texto; serve para leitura, não é versão corrigida ou reprodução fiel para entrega. Nenhum DOCX original foi alterado, nem se afirmou edição oficial vigente pela simples leitura.
|
||||
|
||||
Também inventariado e conservado **Novo Modelo A v1.docx**, 532100bytes, SHA25663d45fa80db17a60b43619d55a25813229d670d14c758273f86316285db8e02b. A mesma imagem é byte a byte igual; a extracção textual difere na segmentação de palavras. São originais distintos, não duplicados por bytes, conservados sem renomear uma revisão como diploma novo. A criação OOXML em21/10/2026 não é data de aprovação ou vigência do formulário.
|
||||
|
||||
Biblioteca QA existente, clone simulator_fiscal_delta_20261021_test: recurso33/documento fbfa820d-29d5-4bf8-9f81-534931d80cef (original) e recurso34/documento cf8b799e-44de-4497-b2e3-a5c468f2fe0e (v1). Pré-visualização, hash, conflitos de identidade/bytes, importação privada idempotente, revisão/auditoria e scan reais reutilizados; ambos limpos com o hash analisado igual ao original. **Não publicados**; consulta privada e estado documental não activam cálculo. Actor166 é fixture QA já existente, configurada apenas neste processo de ensaio da administração da biblioteca, sem atribuir função real da Tempo ou alterar permissões persistentes. Nenhuma importação no destino activo. A aceitação oficial da Tempo08/10 é preservada; os estados genéricos do importador não a revogam nem certificam vigência.
|
||||
|
||||
## Correspondência dos quadros
|
||||
|
||||
| Quadro/bloco no exemplar | Finalidade e dados existentes | Delta/limite |
|
||||
|---|---|---|
|
||||
|1 — Tipo de declaração|Inicial/substituição; não corresponde a criar versão de uma projecção|Não simular uma declaração efectiva; futura ligação a declaração anterior separada de versões de planeamento|
|
||||
|2 — Período/prazo|Meses da projecção disponíveis; não prova de entrega dentro/fora do prazo|Calendário fiscal e entrega efectiva por integrar; nunca marcar submissão pela data da simulação|
|
||||
|3 — NUIT/sucursal/Área Fiscal|Identificação guardada no dossier quando disponível|Reutilização com confirmação específica; não exigir NUIT para planear nem inferir enquadramento fiscal|
|
||||
|4 — Nome/designação|Identificação privada existente|Não copiar para documento público; tratar como dado efectivo só se confirmado|
|
||||
|5 — Actividade/CAE|Actividade/sector existentes; não garantem CAE ou taxa|Correspondência CAE e classificação por operação, sem converter sector em incidência|
|
||||
|6 — Domicílio fiscal|Localização e contactos do dossier|Domicílio do projecto não prova domicílio fiscal; recolher apenas diferença/confirmar contexto|
|
||||
|7 — Inexistência de operações|Zero de vendas é conhecido, mas aquisições podem existir|Não assinalar inexistência por vendas zero ou campos vazios; precisa de todas as operações activas/passivas|
|
||||
|8 — Apuramento|Campos01–18; componente01/02 disponível no âmbito actual|Demais bases, imposto suportado/dedutível e regularizações continuam indeterminados|
|
||||
|Utilização de créditos anteriores|Campos19/20; o texto remete novamente para quadro8|Registar remissão ambígua; créditos efectivos e comunicados distintos de estimativas/pedidos|
|
||||
|9 — Substituições|Campo21 e referências17/18 da actual/substituída|Guardar a relação entre declarações efectivas; uma nova simulação não substitui declaração|
|
||||
|10 — Entrega ao Estado|Campos22–24, meios de pagamento|Sem pagamento ou guia gerada; caixa prevista não prova liquidação|
|
||||
|11 — Recuperação|Campos25–27, reporte e pedido|Separar crédito, pedido, reconhecimento e recebimento; não gerar reembolso automático|
|
||||
|12 — Autenticação|Data/nome/assinatura do sujeito passivo|Autenticação do portal não assina nem submete Modelo A|
|
||||
|13 — Serviços|Entrada/inserção/receita/funcionário/recebedor|Reservado à AT; não preencher ou imitar aceitação administrativa|
|
||||
|
||||
## Correspondência campo a campo
|
||||
|
||||
Os rótulos de finalidade são explicativos; não corrigem a imagem nem substituem o original. Fórmulas impressas estão mapeadas, não activadas por este registo. Ausência de componente não equivale a zero.
|
||||
|
||||
|Campo do Modelo A|Finalidade|Origem dos dados|Cálculo|Fundamento|Já implementado/em falta|
|
||||
|---|---|---|---|---|---|
|
||||
|01|Base das operações à taxa geral|Vendas mensais já projectadas, declaradas sem IVA e no âmbito geral|Base líquida do mês|CIVA arts.15 e 17; Lei22/2022; regra publicada existente|Implementado no âmbito declarado|
|
||||
|02|IVA à taxa geral|Resultado mensal congelado no servidor|Campo01 × taxa da versão publicada /100|CIVA art.17(1); mesma regra/versionamento do consumidor|Implementado; componente do apuramento|
|
||||
|03|Base de operações específicas à taxa reduzida|Operações separadas e enquadradas; ainda não recolhidas|Base das operações elegíveis|Exemplar e CIVA art.17-A, aditado pela Lei22/2022; enquadramento a cadastrar|Em falta; não assumir zero|
|
||||
|04|IVA à taxa reduzida|Campo03 e futura versão fundamentada|Campo03 × taxa reduzida aplicável; não activa pelo exemplar|CIVA art.17-A; âmbito/versão temporal a integrar|Em falta; não assumir zero|
|
||||
|05|Vendas isentas com direito à dedução|Vendas classificadas por operação e fundamento|Soma das bases classificadas; sem taxa presumida|Exemplar; CIVA arts.9–14 e 18–20 a consolidar por operação|Em falta; não assumir zero|
|
||||
|06|Operações da alínea b), n.º1, art.18, conforme exemplar|Operações classificadas com referência verificada|Soma separada; não inferir do nome do sector|Remissão do exemplar ao art.18(1)(b); cotejo do âmbito necessário|Em falta; não assumir zero|
|
||||
|07|Isentas sem direito à dedução|Vendas classificadas e uso das aquisições|Soma das bases elegíveis|Exemplar; incidência/isenções e exclusões de dedução a fundamentar|Em falta; não assumir zero|
|
||||
|08|IVA dedutível — imobilizado|Aquisições/facturas de activos, não o valor global do investimento|Apenas imposto elegível documentado|CIVA arts.18–21; art.18(2), Lei10/2025: documentos legais e fornecedor inscrito|Em falta; não assumir zero|
|
||||
|09|IVA dedutível — existências|Compras projectadas reutilizáveis; falta IVA, documento e elegibilidade|IVA elegível das existências, não compras × taxa geral|CIVA arts.18–21; condições/exclusões, afectação e período|Em falta; não assumir zero|
|
||||
|10|IVA dedutível — outros bens/serviços|Aquisições discriminadas; despesas agregadas não provam IVA|IVA elegível; excluir operações sem direito|CIVA arts.18–21; art.19(2), Lei10/2025, inclui exclusão das operações art.17-A|Em falta; não assumir zero|
|
||||
|11|IVA dedutível — importações|Documento aduaneiro legal e afectação; ainda não recolhidos|Imposto aduaneiro elegível, não taxa sobre orçamento|CIVA art.18(2), Lei10/2025; demais condições/exclusões a cadastrar|Em falta; não assumir zero|
|
||||
|12|Regularizações a favor do sujeito passivo|Ajustamentos individualizados, documento e motivo|Soma de regularizações elegíveis|Exemplar tem remissão interna 11/12 divergente das caixas12/13; fundamento por motivo por conferir|Em falta; não assumir zero|
|
||||
|13|Regularizações a favor do Estado|Ajustamentos individualizados, documento e motivo|Soma separada das regularizações elegíveis|Mesma divergência; não corrigir o original nem inventar incidência|Em falta; não assumir zero|
|
||||
|14|Soma das bases do quadro de apuramento|Campos01,03,05,06,07 completos|01+03+05+06+07|Fórmula impressa no exemplar; não é apenas vendas tributáveis|Estrutura mapeada; total indeterminado|
|
||||
|15|Total a favor do sujeito passivo|Campos08–12 completos|08+09+10+11+12|Fórmula do exemplar; depende de deduções/regularizações fundamentadas|Estrutura mapeada; total indeterminado|
|
||||
|16|Total a favor do Estado|Campos02,04,13 completos|02+04+13|Fórmula impressa no exemplar; 02 sozinho não equivale a16|Estrutura mapeada; total indeterminado|
|
||||
|17|Diferença devedora antes de créditos anteriores|Totais15/16 completos|16−15 quando16>15|Instrução do exemplar; não é automaticamente pagamento|Indeterminado; sem consolidação completa|
|
||||
|18|Diferença credora antes de créditos anteriores|Totais15/16 completos|15−16 quando15>16|Instrução do exemplar; não constitui reembolso reconhecido|Indeterminado; sem consolidação completa|
|
||||
|19|Excesso a reportar do período anterior|Histórico fiscal anterior confirmado, separado de estimativas|Crédito anterior utilizável; sem copiar crédito estimado|Quadro de utilização de créditos do exemplar; condições/prazo por conferir|Em falta; não assumir zero|
|
||||
|20|Créditos comunicados pelos serviços|Comunicação efectiva da Administração Fiscal|Crédito efectivamente comunicado e utilizável|Exemplar; não converter pedido ou crédito estimado em comunicação|Em falta; não assumir zero|
|
||||
|21|Diferencial de declaração de substituição|Valores17/18 da declaração actual e da substituída|Casos impressos no quadro9, com sinal devedor/credor|Exemplar; ligação às declarações efectivas e regras de substituição em falta|Em falta; não assumir zero|
|
||||
|22|IVA a entregar no contexto da declaração|Apuramento e créditos/substituição completos|Não derivar apenas do campo02|Quadro10 do exemplar; procedimento aplicável por fundamentar|Em falta; não assumir zero|
|
||||
|23|Juros compensatórios|Factos e períodos que legalmente os determinem|Sem taxa, mora ou obrigação presumidas|Quadro10; fundamento específico não analisado neste lote|Em falta; não assumir zero|
|
||||
|24|Importância a pagar|Campos22/23 completos|22+23|Fórmula do exemplar; não é ordem nem execução de pagamento|Em falta; não assumir zero|
|
||||
|25|Crédito de imposto do quadro11|Apuramento completo e tratamento dos créditos anteriores|Montante recuperável no contexto próprio, por fundamentar|Caixa25 confirmada visualmente no bloco de recuperação; condições por consolidar|Em falta; não assumir zero|
|
||||
|26|Reporte para período seguinte|Crédito utilizável e opção no quadro11|Separado de pedido e recebimento|Quadro11; cotejo de condições/prazos e articulação19/20 necessário|Em falta; não assumir zero|
|
||||
|27|Pedido de reembolso|Crédito elegível e pedido, sem presumir reconhecimento/caixa|Pedido distinto de aprovação e recebimento|Exemplar cita art.20; Lei10/2025 altera reembolsos no art.21. Divergência registada.|Em falta; não assumir zero|
|
||||
|
||||
## Cotejo documental para o período2026
|
||||
|
||||
Reutilizados os exemplares oficiais aceites e arquivados em source-archive, a fichaIVA existente e a análise primária; não se pede nova autenticação genérica das fontes. Leitura dirigida agora: Lei22/2022, art.2/aditamento17-A, PDF4/BR2252-(28), e art.6, eficácia01/01/2023; Lei10/2025 no BR248/suplemento1, PDF12–15, especialmente arts.18,19,21 na PDF13/BR2180-(13), arts.32/34 na PDF14. Páginas13 e14 de2025 e página4 de2022 renderizadas para confronto. Não se afirma revisão integral de todo o CIVA ou de todas as alterações/regulamentos.
|
||||
|
||||
- Campo01/02: taxa geral e base líquida já fundamentadas pela ficha existente; não reabrir a prova do motor.
|
||||
- Campo03/04: o art.17-A aditado pela Lei22/2022 identifica serviços médicos/sanitários de estabelecimentos privados indicados; ensino por estabelecimentos privados integrados e reconhecidos; formação profissional por entidades privadas; lições pessoais escolares/superiores. Esta enumeração não classifica automaticamente uma empresa ou toda a sua facturação. Falta cadastro/integração do âmbito temporal e classificação por linha; não se publica uma regra5% com base apenas no formulário.
|
||||
- Aquisições: Lei10/2025, art.18(2), exige os documentos legais indicados e inscrição do fornecedor; art.18(6) trata proporcionalidade em certas bases reduzidas. Art.19(2) exclui o direito relativo às operações do art.17-A e às indicadas no art.15(6)(c). Portanto, o valor das compras ou o IVA suportado não podem ser automaticamente dedutíveis. Arts.18(1),19(1),20,21 e22, exclusões, afectação, pro rata e momento de dedução precisam de consolidação para cada futuro lote; os trechos[...] da lei alteradora não substituem o texto-base.
|
||||
- Reembolso: a nota do exemplar cita art.20. A Lei10/2025 altera no art.21 condições e prazos de reembolso/crédito, incluindo números7/8/10/13/15. Registar a divergência; não transplantar prazos ou limites isolados para regras de elegibilidade. Crédito não é recebimento garantido.
|
||||
- Preços com IVA incluído: art.34 na redacção de2025 prevê método específico para apurar a base e referência a arredondamento nesse contexto. Não é regra universal para o cálculo líquido, nem autorização para aplicar16% ao bruto. O consumidor actual continua a preservar o bruto e a não o calcular. A extensão requer implementação própria e casos dirigidos; não reabre como bloqueio o arredondamento já resolvido para base líquida.
|
||||
- Declaração/período: art.32 alterado em2025 distingue situações/prazos. Não se decide entrega fora/dentro do prazo por ser uma projecção futura. O exemplar instrui terminar no quadro8 em certos casos; essa instrução não está activa como regra de software.
|
||||
|
||||
**Divergências preservadas:** linha de regularizações diz campo11/12 mas caixas12/13; utilização de créditos cita campos18/19, enquanto as caixas desse bloco são19/20; nota de reembolso cita art.20 e as alterações de2025 tratam a matéria no art.21; remissões de quadro8 são ambíguas; a repetição25 foi um artefacto da extracção textual, não uma duplicação de campo no formulário, cuja caixa25 foi confirmada visualmente. Não se fez correcção silenciosa nem se apresentou uma transcrição com ambiguidades como fórmula legal inequívoca.
|
||||
|
||||
Pesquisa oficial21/10/2026: [página AT Modelo A](https://edeclaracao.at.gov.mz/formularios/iva/view/formulario_iva_ma.aspx) tem resultado indexado com apuramento no quadro5, diferente do quadro8 deste exemplar. A abertura directa expirou; não se descarregou formulário substituto nem se certificou qual é a edição vigente. [INM BR248/2025](https://inm.gov.mz/pt-br/content/sum%C3%A1rio-suplemento-n%C2%BA-1-de-291225-pag-2180-1-20-br-n%C2%BA-248-boletim-da-rep%C3%BAblica-i-serie-p%C3%A1g-1) identifica a Lei10/2025. O [comunicado governamental de30/09/2026](https://portaldogoverno.gov.mz/governo-introduz-alteracoes-nos-regulamentos-fiscais-para-reforcar-eficiencia-tributaria/) anuncia alterações regulamentares; não fornece por si o articulado para cadastrar novos critérios. A edição exacta aplicável do formulário, correspondência da Lei3/2025 referida na Lei10/2025 e regulamentos pertinentes permanecem por localizar/consolidar no lote respectivo. Nenhuma destas pendências é convertida em bloqueio genérico do cálculo líquido16% já aceite.
|
||||
|
||||
Inventário complementar sem utilização automática: Downloads/IVA-2025.pdf(83páginas, consolidação com leis listadas na capa, origem oficial do exemplar ainda não identificada), iva.pdf(8páginas sem texto extraível nas duas primeiras) e alteracao CIVA2023(18páginas, BR250/2022, já corresponde ao acervo arquivado) foram localizados. Não se usa a consolidação de programação ou uma cópia sem identificação como substituto do BR. O inventário global anterior e as fontes não analisadas permanecem conservados.
|
||||
|
||||
## Delta entregue e execução progressiva restante
|
||||
|
||||
**Entregue nesta candidataQA:** correspondência consultável dos27campos, quadros documentados, resumo mensal rotulado como simulação parcial01/02, notas no relatório sem reescrever os snapshots aceites, reutilização das compras/encomendas activas (por produto, calendário ou constante) mantendo zero distinto de falta e base/IVA/dedução separados e indeterminados. Não transforma custo das vendas, depreciação, pagamento de fornecedor, investimento global ou compra no imposto dedutível. Sem taxa nova no formulário, novo motor, migration, regra publicada ou activação.
|
||||
|
||||
**Próximo lote implementável delimitado:** classificação fiscal por operação de venda e aquisição, com pressuposto ou dado efectivo identificados; tipos de documento, datas, fornecedor/inscrição quando aplicável, utilização e exclusões; separar net/IVA/gross sem repetir quantidade/preço. Reutilizar planos como fontes, pedir apenas a classificação e o que falta; registar ligações/versionamento do dado de origem, não copiar e divergir. Dados efectivos não substituem previsões históricas nem herdam automaticamente as declarações de âmbito da projecção. No cadastro, configurar versões/âmbitos/condições da taxa5% e das deduções apenas após consolidar os dispositivos pertinentes; testes de operações excluídas e entradas insuficientes antes de publicar. Depois ligar totais14–18 através do avaliador existente, propagando indeterminação até todas as parcelas estarem conhecidas ou explicitamente sem operações. Num lote posterior, créditos anteriores/substituições/pedido e liquidação, preservando comunicação/reconhecimento/caixa separados. A publicação/activação são operações expressas distintas da integração do código.
|
||||
|
||||
Estimativa de saldo, não horas medidas nem compromisso de cobertura integral: **cadastro/consolidação8–16h** (classificação5%, deduções do primeiro âmbito, exemplos e correspondências) e **código/aceitação20–36h** (dados por operação8–14h; ligação a versões/dedução e totais8–14h; histórico/relatório/coerência4–8h), **28–52h** para o próximo lote delimitado. Créditos anteriores, substituições, pagamento/reembolso e cobertura integral ficam fora desta estimativa e exigem delimitação própria; não reutilizar as antigas15–26h como estimativa desta ampliação. Perguntas concretas ainda abertas: edição/norma de aprovação do formulário para o período e dispositivos consolidados pertinentes; não pedir à Tempo nova validação da credibilidade dos BR aceites.
|
||||
|
||||
Critérios do lote seguinte: divisão de uma mesma venda entre enquadramentos sem duplicação; aquisições elegíveis/não elegíveis/mistas, documento ausente e fornecedor não inscrito; zero diferente de falta; não deduzir17-A; data relevante por componente; totais nulos com parcelas indeterminadas; alteração de origem cria versão e invalida dependentes, sem reescrever efectivos/previstos anteriores; relatório mostra período/fonte/regra e não gera declaração aceita, guia ou promessa de reembolso. Suspensão e históricos reutilizam o mecanismo existente.
|
||||
|
||||
Provas deste delta:3testes frontend novos (prioridade dos calendários de compras, zero/falta e ausência de dedução presumida, adenda sem mutar resultados antigos); TypeScript/build; importação privada/hash/scan e repetição idempotente. Conferência Edge apenas da nova correspondência, compras e relatório recuperado, sem calcular novamente nem repetir CSV/PDF aceites. Provas privadas em outputs/modelo-a-20261021. MatrizAUD-SIM-INT02, inventário documental e recibo existentes actualizados; main e homologação intactos.
|
||||
@@ -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."
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"functional_candidate": "1bcf34c0",
|
||||
"installation_procedure": "e46f9731",
|
||||
"installed_revision": "f4d0970917e7e8d141f74c136b66138b929141fc",
|
||||
"activated_utc": "2026-10-22T20:51:27.874333+00:00",
|
||||
"counts": {
|
||||
"tenders": 1,
|
||||
"versions": 1,
|
||||
"published_versions": 0,
|
||||
"watches": 0
|
||||
},
|
||||
"verification_draft": {
|
||||
"id": "b970f0f0-9257-42e1-8365-3358237b55db",
|
||||
"created_by": 15,
|
||||
"version": 1,
|
||||
"published_version_id": null
|
||||
},
|
||||
"verification": {
|
||||
"real_account_id": 15,
|
||||
"mfa_renewed_by_user": true,
|
||||
"management_hub": true,
|
||||
"draft_saved_recovered": true,
|
||||
"preview_opened": true,
|
||||
"publication_performed": false,
|
||||
"qa_proofs_reused": "8 tests / 285 assertions; 6 navigation tests; build and TypeScript"
|
||||
},
|
||||
"links": {
|
||||
"management": "https://teste-investidor.tempo.co.mz/gestao-conteudos",
|
||||
"catalogue": "https://teste-investidor.tempo.co.mz/concursos"
|
||||
},
|
||||
"backup": {
|
||||
"database_sha256": "69de817b288ecb25db4b341375475f2408a47642a8ba69d338b217dcc982854f",
|
||||
"files_sha256": "7b75c22e730b5600d6e97e1c6dbb4cd93982bdd47e9dd2a63de2359974132674",
|
||||
"tables_restored_verified": 171,
|
||||
"files_archive_verified": 341
|
||||
},
|
||||
"preservation": {
|
||||
"protected_tables_unchanged": [
|
||||
"users",
|
||||
"portal_role_assignments",
|
||||
"documents",
|
||||
"portal_resources",
|
||||
"opportunities",
|
||||
"geo_locations",
|
||||
"geo_geometries"
|
||||
],
|
||||
"original_files_preserved": 340
|
||||
},
|
||||
"private_document_anonymous_status": 404
|
||||
}
|
||||
@@ -1,5 +1,291 @@
|
||||
# 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.
|
||||
|
||||
| Trabalho | Percurso pela interface |
|
||||
| --- | --- |
|
||||
| Oportunidades | **Nova oportunidade** → escolher promotor real e modalidade → preencher → **Guardar rascunho**. Recuperar em **Gerir oportunidades e histórico**; abrir a ficha, completar **Imagens, documentos e localizações** e **Submeter para validação**. O Validador usa a fila de revisão e as acções **Iniciar validação**, **Guardar revisão especializada**, pedir correcções ou **Aprovar versão**, conforme a tarefa atribuída. O Publicador usa a fila de publicação e **Publicar no catálogo público** na versão aprovada com visibilidade pública. A equipa conclui as funções que já lhe foram atribuídas; a criação não concede revisão nem publicação. |
|
||||
| Documentos | **Novo documento** → escolher ficheiro e origem conhecida → **Guardar em quarentena** → conferir a análise indicada no ecrã → completar **Registar documento na biblioteca** → **Guardar como rascunho**. Recuperar pela pesquisa em **Gerir conteúdos, versões e histórico**; usar **Editar metadados** para rever; guardar as alterações e usar **Publicar**. Se o scanner comunicar falha, usar **Tentar novamente**; avaria persistente é manutenção técnica, não uma nova aprovação documental. |
|
||||
| Legislação | **Nova legislação** → mesmo percurso documental, com tipo Legislação pré-seleccionado → registar entidade, número, edição, fonte, datas e situação conhecida → guardar → pesquisar/reabrir → rever metadados/associações → **Publicar**. Fonte oficial identificada mantém a aceitação da Tempo; não exige segunda certificação. A publicação permite consulta e não activa regras fiscais. |
|
||||
|
||||
Para retirar um documento, abrir o registo e usar **Retirar da publicação**; original e histórico ficam preservados. Para oportunidades, usar as acções de suspensão/arquivo apresentadas à função responsável. Não se apagam registos para os retirar do público.
|
||||
|
||||
Este guia descreve os percursos existentes; a instalação de código é manutenção pelo processo Git automático. Contas ainda não criadas conservam as designações aprovadas e não suspendem os acessos das contas existentes. Conferência real desta instalação registada na secção de entrega, abaixo; o guia não substitui essa evidência.
|
||||
|
||||
## Parques — disponibilizados em18/10/2026
|
||||
|
||||
Revisão `parques-integral-reconciliado-639ed9624a48` disponibilizada em `https://portal-investidor.test`, com autorização expressa da Tempo. Sete fontes (cinco funcionais e dois auxiliares gerados) e170 ficheiros de build; nenhuma migration executada. L1/140000, navegação/150000 e editorial160000 preservados. Backup/restauro fresco e preservação de dados conferidos. Os dados reais desconhecidos mantêm-se desconhecidos; não se encerram todos os requisitos da ficha pelo nome integral.
|
||||
@@ -115,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 |
|
||||
@@ -1006,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
|
||||
|
||||
@@ -1980,3 +2274,506 @@ As notas aparecem junto dos campos e num resumo com ligações que focam o campo
|
||||
No Edge, simulador normal QA em127.0.0.1:18580: consulta anual2026, operação genérica explicitamente escolhida para o ensaio; moeda inicialmente vazia, depoisUSD escolhido expressamente como caso dirigido. Confirmaram-se notas, resumo, foco em «Rever moeda»/«Rever operação», fundamento da versão sintética3, eUSD/ano/periodicidade/operação conservados após gravação e recuperação do mesmo rascunho. A captura de prova é um ensaioUSD, não evidência deUSD no caso original. Não se executou cálculo nem se exportou relatório. A fixture publicada apenas emQA para observar o diagnóstico foi depois suspensa; o consumidorQA permaneceu desligado. Nenhuma fixture foi importada, regra alterada ou consumidor activado na homologação nesta correcção.
|
||||
|
||||
Provas privadas na pasta `outputs/simulator-continuation-20261020`: iva-diagnostic-api-test.txt, iva-diagnostic-summary.txt, iva-diagnostic-summary.png e iva-diagnostic-build-final.txt. O reciboJSON existente tem o estado detalhado. A candidata permanece na branch `codex/iva-diagnostics-QA`, separada de main; não é uma nova publicação fiscal nem altera a entrega autorizada anteriormente.
|
||||
|
||||
|
||||
### IVA mensal ligado às vendas — candidata QA, 21/10/2026
|
||||
|
||||
Pedido da Tempo: o formulário recolhe pressupostos do negócio e calcula o IVA correspondente, sem repetir vendas nem tomar o imposto manual como entrada fiscal. A falta de ligação entre a projecção empresarial e o consumidor publicado era uma escolha de implementação; não uma nova obrigação do utilizador. Alteração na branch `codex/iva-monthly-projection-QA`, separada de main. Não altera a instalação, regra ou activação da homologação.
|
||||
|
||||
**Ligação:** o simulador normal reutiliza quantidades e preços por produto/mês do plano empresarial, com a mesma representação monetária das vendas já usada nesse plano. Se não existir plano, admite a receita mensal constante já guardada na projecção personalizada, identificando essa origem e exigindo declaração do enquadramento. Não usa os valores demonstrativos de um widget ainda não guardado. A indicação sem IVA/com IVA incluído fica explícita, sem a presumir. Neste lote só a base líquida declarada permite calcular: preços com IVA incluído, base desconhecida e meses sem vendas informadas permanecem indeterminados. Zero explícito calcula IVA zero; não se preenche um mês vazio com zero.
|
||||
|
||||
O cliente envia meses, vendas, moeda e declaração; não envia taxa, versão ou imposto manual. O servidor selecciona a versão publicada ligada ao consumidor e reutiliza o resolvedor temporal e avaliador decimal existentes em cada mês, dentro da mesma admissão transaccional. Nenhuma taxa foi fixada no formulário, nem introduzido arredondamento fiscal. A versão é congelada antes da fila; mantém-se a autorização/idempotência/histórico/relatório existentes. Sem migration ou reconstrução dos mecanismos. Cobertura permanece 2026, no máximo 12 meses consecutivos; um horizonte que atravessa o limite é explicado, sem truncar a projecção.
|
||||
|
||||
**Ecrã e dependências:** cada mês apresenta vendas declaradas, vendas tributáveis sem IVA, taxa efectivamente aplicada, IVA calculado e total com IVA. Após a primeira execução, a actualização automática fica ligada e pode ser pausada. Alterar vendas, moeda, período ou tratamento do IVA assinala imediatamente o resultado anterior como histórico desactualizado. A nova execução guarda outra versão sem modificar a anterior. Repetir sem alterações recupera a execução concluída; uma execução pendente é consultada pelo mesmo ID. Falhas conservam os pressupostos e permitem tentar novamente. O rascunho conserva configurações e resultado; o relatório distingue resultado actual e histórico desactualizado e não recalcula o IVA na exportação. Se algum mês estiver indeterminado, o total fiscal fica indeterminado, conservando os meses conhecidos.
|
||||
|
||||
**Tesouraria adoptada:** o IVA é um memorando fiscal separado; não se soma novamente aos recebimentos introduzidos, não se deduz como custo e não se apresenta como imposto final a pagar. Conserva-se o calendário de recebimentos/pagamentos declarado, sem inferir cobrança integral no mesmo mês nem liquidação fiscal. O imposto manual permanece exclusivamente no painel recolhido «Cenário personalizado de tesouraria — imposto manual, separado do IVA calculado», com rótulos de cenário personalizado. Não alimenta a base nem o resultado do consumidor IVA. Não foram acrescentados deduções, créditos, reembolsos, importações, isenções, taxas reduzidas ou regimes especiais ao cálculo fiscal; as hipóteses antigas de tesouraria continuam personalizadas e separadas.
|
||||
|
||||
**Provas apenas do delta:** 3 testes API novos/46 asserções, transacções no clone `simulator_fiscal_checks_20261021_test` com guarda QA; 4 testes frontend novos para reutilização das vendas, zero/informação em falta, invalidação e recuperação/relatório. Incluem taxa proibida no cliente, âmbito não declarado, preços brutos, período excluído, repetição sem duplicações, versão congelada antes da suspensão e conclusão depois dela. TypeScript/build/Pint/diff-check verificados. Reutilizadas as provas aceites do avaliador decimal, exportadores e PDF; não se voltou a testar paginação nem a ampliar cobertura legal.
|
||||
|
||||
**Percurso Edge no simulador normal:** dossier sintético privado «QA IVA mensal - vendas projectadas», ID `e0201d19-286e-4d5c-9585-2c1e3bd1ad1e`, QA127.0.0.1:18580. Vendas Janeiro1000.03/ Fevereiro2000.06/ Março0/ Abril vazio produziram IVA160.0048/320.0096/0/indeterminado. Alterar apenas a quantidade de Janeiro de1 para3 actualizou vendas3000.09, IVA480.0144 e total3480.1044. Assinalou-se resultado desactualizado durante a mudança. Seleccionar preços com IVA incluído conservou vendas e tornou base/IVA/total indeterminados; voltar manualmente à base líquida gerou nova execução. Após recarregar e recuperar o mesmo dossier, os valores permaneceram iguais e o fundamento mostrou a versão congelada. Quatro execuções mensais intencionais (inicial, venda alterada, base incluída, regresso à líquida), quatro relatórios com snapshots iguais às respectivas execuções; recuperação não criou a quinta. Não se atribui aplicabilidade fiscal universal a estes exemplos aritméticos.
|
||||
|
||||
A fixture sintética **v4**, ID `01a15e50-7972-72e0-8991-82ca28df9b49`, está publicada e o consumidor ligado **apenas no clone QA** `simulator_fiscal_delta_20261021_test`, deixados disponíveis para esta revisão. Isto substitui o estado local suspenso/off da prova diagnóstica anterior; não é indisponibilidade na homologação. Não importar esta fixture, os seus actores ou os scripts privados. A regra real de homologação v1 mantém-se fora desta execução.
|
||||
|
||||
**Revisão e disponibilização:** rever esta candidata QA e integrar somente o código autorizado, mantendo a ligação ao ID/versão reais do destino. Este delta não precisa de publicar outra regra nem de mudar a taxa/âmbito ou de reactivar um consumidor já activo. Conferir apenas revisão servida e ligação mensal no destino após autorização própria; conservar o procedimento separado de publicação e activação se alguma dessas operações vier a ser expressamente pedida. Suspender novas admissões pelo interruptor existente ou estado da regra preserva as já admitidas e os históricos. Não apagar snapshots para suspender ou reverter código. Sem integração em main, escrita activa, validação jurídica atribuída à Tempo ou nova ronda de preparação identificada nesta entrega QA.
|
||||
|
||||
Provas privadas em `outputs/simulator-continuation-20261020`: monthly-iva-api-test.txt, monthly-iva-build-final.txt, monthly-iva-initial.txt, monthly-iva-updated.txt, monthly-iva-stale.txt, monthly-iva-included.txt, monthly-iva-recovered.txt, monthly-iva-live-history.json e monthly-iva-proof.png. O recibo existente conserva o saldo e os IDs; AUD-SIM-INT02 da matriz existente recebe esta referência, sem outra matriz/backlog.
|
||||
|
||||
|
||||
### Reconciliação Modelo A — primeiro incremento QA,21/10/2026
|
||||
|
||||
Por instrução expressa da Tempo, o **Novo Modelo A.docx** é a referência estrutural, incluindo o quadro incorporado como imagem; não foi substituído por um modelo genérico. A [reconciliação campo a campo](../simulations/modelo-a-reconciliacao-20261021.md) contém todos os quadros, campos01–27, finalidade, origem, cálculo, fundamento e estado; inventário/versões/exemplares e divergências preservados. A componente16% existente representa apenas01/02. O apuramento14–18 não se presume igual ao IVA das vendas; não é saldo final a pagar.
|
||||
|
||||
Original532008bytes/SHA25662c198e90d8c6792f7d5ccefc6386df2e4b6089573067d3a6e1eae7fc2b9572b e variantev1 de532100bytes/63d45fa80db17a60b43619d55a25813229d670d14c758273f86316285db8e02b preservados na biblioteca **privada QA**, recursos33/34, documentos e revisões/auditoria existentes. Importador existente, pré-visualização, conflitos/duplicados, scans reais limpos com mesmohash e nova tentativa sem criar documentos ou scans. Actor166 é a fixture administrativa do ensaio isolado, sem papel real ou alteração persistente de permissões atribuída à Tempo. Nenhuma publicação ou importação na homologação. Aceitação das fontes oficiais da Tempo08/10 mantida; edição vigente do formulário não afirmada pela data OOXML nem pela leitura do anexo.
|
||||
|
||||
No simulador normal, resumo de simulação parcial e correspondência consultável ligados ao resultado mensal recuperado. Compras/encomendas projectadas reutilizadas conforme agenda activa por produto, calendário ou valor constante; não são custo das vendas ou pagamentos de fornecedor. Zero difere de falta. A indicação semIVA/IVA/comIVA/dedutível fica separada e por determinar quando faltam classificação e documentos; não se considera toda compra dedutível. Relatório acrescenta correspondência/limites e referências dos pressupostos actuais de compras, sem os apresentar como resultado fiscal histórico nem reescrever snapshots. Nenhuma nova fórmula fiscal activada ou taxa fixada no formulário; sem migration ou motor novo.
|
||||
|
||||
Cotejo dirigido: Lei22/2022 art.17-A/aditamento e eficácia; Lei10/2025 arts.18/19/21/32/34 nas páginas12–15 do BR248, com leitura visual13/14. Registadas remissões divergentes do exemplar (regularizações11/12 versus caixas12/13; crédito18/19 versus caixas19/20; reembolsoart20 versus matéria noart21; quadro8 ambíguo). Não corrigir original nem aplicar instrução ambígua como regra. AT directa expirou; resultado indexado com numeração distinta é limitação de consulta, não substituto nem prova de edição vigente. Lei3/2025/dispositivos/regulamentos pertinentes por consolidar na extensão concreta, sem reabrir genericamente a base líquida16% aceite.
|
||||
|
||||
Provas novas apenas do incremento:3testes frontend (agendas, zero/falta, sem IVA/dedução fabricados e relatório sem mutar resultado), TypeScript/build, Edge de consulta após recuperação e biblioteca idempotente/scan. Avaliador, selecção publicada, precisão, históricos e exportadores aceites reutilizados. DossierQA e fixtureIVA v4 continuam disponíveis para revisão; este incremento não calcula novamente. Provas privadas outputs/modelo-a-20261021; recibo e inventário existentes actualizados. Branch codex/iva-modelo-a-reconciliation-QA, parent3f5430c3, separada de main; homologação/regra real/interruptor inalterados.
|
||||
|
||||
Saldo explícito: **reconciliação e primeiro incremento de interface concluídos, apuramento completo não implementado**. Próximo lote: classificação por operação e origem prevista/efectiva, cadastro fundamentado de âmbito/dedução e ligação a totais14–18 propagando indeterminação; 8–16h cadastro/consolidação e20–36h código/aceitação, total28–52h condicionado ao âmbito/consolidação, sem créditos anteriores/substituições/pagamento/reembolso ou simulador integral. O documento contém o delta e critérios concretos. Integração do código, publicação e activação continuam operações distintas; esta preparação não executa nenhuma no destino.
|
||||
|
||||
|
||||
### Continuidade e adaptação contextual — primeiro incremento QA, 22/10/2026
|
||||
|
||||
Instrução expressa da Tempo de 22/10: o contexto do investimento é transversal ao percurso, formulários, documentos e simulações, mesmo sem IA. Base Git e revisão servida confirmadas em leitura: `57fc914821d0cefa02caa80a74b3d42e663f2742`. Trabalho isolado em `codex/project-context-continuity-QA`, sem integração ou publicação/activação fiscal no destino.
|
||||
|
||||
Causas identificadas no código e reprodução dirigida: `InvestorContext` só distribuía título, descrição, sector e geografia; o preenchimento comum copiava apenas campos vazios sem distinguir herança de edição deliberada; o IVA pontual mantinha moeda/data/base em estado local que desaparecia ao desmontar o ecrã. A projecção empresarial sobrepunha a moeda do projecto à moeda de origem do plano ao apresentar/calcular/exportar. No ensaio anterior, projecto guardado em MZN e selector IVA vazio. A primeira tentativa de abertura encontrou uma sessão expirada, confirmada pela leitura sem autenticação e resolvida pelo início de sessão normal; não foi classificada como perda do dossier.
|
||||
|
||||
Correcção comum: consolidar exclusivamente o rascunho explicitamente ligado ao mesmo dossier e proprietário; conservar origem, versão e hash. Não procurar o último projecto da conta. Herança persistida com assinaturas de alteração por campo e âmbito dossier/consumidor; não são tokens de autorização nem prova jurídica. Actualizar valores ainda herdados, preservar alterações deliberadas e campos apagados; manter a hierarquia geográfica e os restantes dados das parcelas. O armazenamento do navegador passa a incluir conta e projecto. Consumidores do padrão: simulador, apoio à terra, Balcão e manifestação de investimento; esta última recebe também a moeda compatível. Campos sem correspondência semântica não são copiados. Nacionalidade, residência e país de constituição não são equiparados.
|
||||
|
||||
O IVA pontual guarda as entradas com o rascunho. A exploração fiscal, oportunidade e componente pontual reutilizam a moeda compatível; a projecção mensal reutiliza o plano e o início previsto. Não se infere exigibilidade fiscal a partir da data prevista de início, nem residência jurídica a partir do país. USD é conservado e explicado como não suportado pela versão MZN. Uma divergência entre moeda guardada no plano e moeda do projecto é apresentada, sem conversão ou reclassificação. O plano conserva a moeda de origem também na apresentação e nos exportadores existentes. Vendas, zero/falta e snapshots de IVA continuam a usar o motor publicado e os históricos aceites; nenhum imposto mensal fixo é introduzido.
|
||||
|
||||
Adaptação deste incremento: o editor existente passa a permitir moeda, início previsto, origem do investidor, tipo/situação da entidade e residência fiscal declarada, sempre pela prévia/confirmar existente. A prévia continua a identificar dados/documentos conservados, resultados e etapas afectados; alterações apenas ao título/descrição não invalidam a aplicabilidade. O selector territorial reutiliza o catálogo público `InvestmentZones` para parques, ZEE e ZFI, preservando versão/hash, fontes e condições públicas já divulgadas no complemento existente. A mesma síntese aparece no contexto e simulador e segue para o relatório do dossier. Os ramos de terra/financiamento reutilizam a definição publicada, factos, estados e razões de `InvestorPath`; não foi criado outro avaliador nem activado benefício.
|
||||
|
||||
Escolher uma zona abre a análise específica: referência consultada, condições documentadas ou em falta, período/fonte cadastrados quando disponíveis e reconhecimento por determinar. O benefício e o cenário condicionado permanecem indeterminados quando não existe ligação executável às condições/reconhecimento. O IVA disponível é identificado como cenário geral sem benefício; não representa automaticamente o regime escolhido nem apuramento final. Reutilizam-se `InvestorContext`, `InvestmentZones`, `InvestmentZoneOperationalDetails`, `InvestorPath`/definição publicada, resolvedor/avaliador fiscal e snapshots existentes. A escolha técnica de exigir uma nova confirmação genérica dos mesmos dados em cada formulário foi retirada por instrução da Tempo; permissões, declarações, submissões formais, revisões independentes aprovadas e campos autorizados de pedidos devolvidos mantêm os controlos existentes.
|
||||
|
||||
Provas novas: 4 testes HTTP/74 asserções, em transacções revertidas na base isolada `simulator_fiscal_delta_20261021_test`/loopback55441; 8 testes frontend de herança, edição/apagamento, isolamento, parcelas, moeda divergente e contexto no relatório; TypeScript/build. Percurso Edge: A nacional/MZN e residência fiscal ZA, vendas guardadas de quatro meses → cálculo mensal → gravação → edição deliberada do título → recarregamento → terminar sessão/iniciar sessão → recuperação e relatório pelos dados efectivamente guardados. Resultado `ae401465-834f-4761-9406-5af060c499fb`, uma única execução nova no dia; Janeiro1000.03/IVA160.0048, Fevereiro2000.06/320.0096, Marçozero e Abrilsem informação. O modelo de relatório recuperado conserva os valores exactos e não altera o snapshot. B estrangeiro/USD, primeiro divergência explícita planoMZN/projectoUSD, depois declaração manual USD no plano, IVA recusado sem conversão; selecção de parque pela prévia e análise específica. Os testes de ZEE/ZFI usam apenas metadados sintéticos revertidos: não demonstram elegibilidade real. Provas privadas em `outputs/context-continuity-20261022`; não entregar fixtures ou credenciais.
|
||||
|
||||
Limites concretos: este é o primeiro incremento utilizável de continuidade e explicação contextual, não a conclusão da adaptação de todos os sectores/regimes. O catálogo QA consultado contém parques sintéticos; a ligação jurídica completa de requisitos/documentos/licenças e benefícios às condições de cada actividade/regime não está demonstrada. Condições sem estrutura/relação executável continuam explicitamente em falta; nenhuma alegação nova de direito ou benefício é feita. Não houve nova validação jurídica nem revalidação genérica de credibilidade das fontes oficiais aceites em08/10. Não repetir cálculo decimal/exportadores/paginação já aceites. A demonstração visual foi feita no percurso e simulador; os outros três consumidores receberam o reconciliador comum, sem se afirmar um ensaio visual integral de cada processo.
|
||||
|
||||
Revisão: abrir o simulador QA com o dossier A `92d0836c-0ffb-4e07-a7f5-0e8ca3f594b9` ou B `1872412f-251a-4f6d-bf18-70388e71a16a`; usar a conta sintética QA existente, nunca actores sintéticos na homologação. O router privado de QA usa a instalação já existente do percurso, activada apenas neste ensaio isolado; não há migration ou nova exigência de funções. Entregar a candidata para revisão pelo processo Git existente, preservando alterações concorrentes. Uma integração futura exige rever o delta relativamente a main e o estado de instalação/configuração do percurso no destino; não publicar regras ou alterar interruptores fiscais como consequência desta correcção. Preservar todos os históricos na suspensão ou reversão de código.
|
||||
|
||||
Fecho da observação deste incremento: o regresso A ao dossier gravou a evidência financeira `43317c22-50c8-40cf-98e4-e5d4d108c372`, rascunho `cf4dec46-c96c-4aa6-aa9c-1a8d6ecb1bea` v8, contexto v1, com hash preservado. A ligação de inspecção do Edge interrompeu-se depois da navegação; o inventário mostrou o endereço do mesmo dossier e a leitura autenticada do serviço confirmou a gravação. A parte visual final do regresso não foi observada e fica como limitação da ferramenta, não falha do simulador. B recuperou contexto v2 e gravou rascunho v6, mantendo USD e sem execução fiscal. O relatório gerado destes dados recuperados conserva a versão do parque e benefício indeterminado. O enriquecimento automático do rascunho privado é exclusivo do proprietário autenticado; o acesso de gestão ao dossier não concede por esta alteração acesso ao rascunho privado da conta.
|
||||
|
||||
|
||||
### Revisão de a76369eb e primeiro percurso adaptativo fundamentado — v1, 22/10/2026
|
||||
|
||||
**Revisão encerrada no âmbito de a76369eb, sem integração.** O último regresso tem prova equivalente: destino do mesmo dossier observado pelo inventário do navegador e evidência `43317c22-50c8-40cf-98e4-e5d4d108c372` confirmada no serviço, ligada ao rascunho v8/contexto v1 e ao hash `a21fa5779bf8b8df366f16683c8a9bb9817a6d2921c7384f85df02135307fa13`. A observação visual final continuou limitada pela ferramenta. Não se repetiram A/B, cálculos, exportações ou nova sessão; reutilizam-se as provas aceites. Leitura dirigida do delta: isolamento por proprietário/dossier, edições/apagamentos, origem monetária, controlo de escrita e ausência de publicação fiscal. Não foi identificada nova preparação técnica que condicione a decisão de integração deste âmbito. A adaptação jurídica seguinte é independente.
|
||||
|
||||
**Percurso delimitado preparado:** preparação de um pedido de investimento privado para empreendimento económico em Moçambique, com actividade concreta declarada, instalação identificada e operações mensais enquadráveis no consumidor IVA geral existente. Trata a preparação do pedido de investimento; não se apresenta como licenciamento sectorial completo, certificado emitido, direito a incentivo ou submissão à Administração. Investidor nacional/estrangeiro, actividade, modalidade e localização são condições distintas. A saída é formulário orientado, etapas/documentos aplicáveis e simulação identificada pelo seu âmbito. Não se escolhe “Turismo” ou uma ZEE para presumir requisitos ou benefícios.
|
||||
|
||||
**Fonte primária aplicada:** compilação oficial APIEX “Legislação de Investimentos em Moçambique”, exemplar obtido de https://pt.apiex.gov.mz/wp-content/uploads/2025/02/LEGISLACAO-DE-INVESTIMENTOS-EM-MOCAMBIQUE.pdf — 6467655 bytes, SHA256 `ba1e9eaabfec5d0c4cb2d9f2444a6860f7a90f222034764197829be402d408ed`. Contém Lei8/2023 (pp.8–32), Decreto8/2024/Regulamento (pp.36–59), CBF (pp.64–92). Aceitação oficial de08/10 mantida; não há exigência de segunda autenticação ou aprovação jurídica da Tempo. Inventários existentes de604 registos/exemplares/anexos e9901 caminhos do acervo reutilizados antes da selecção; cópia privada, texto extraído e citações em `outputs/adaptive-path-20261022`. Não importar/publicar automaticamente. As pendências históricas genéricas de nova certificação nos levantamentos de28/09 e03/10 não são gates deste lote.
|
||||
|
||||
**Período:** Decreto8/2024, artigo5 do diploma preambular, indica aplicação desde a publicação de7/03/2024; Lei8/2023, artigo32, indica90dias após publicação. O lote proposto cobre datas de projecto em2026 e preserva estes inícios, exemplar e versão. Não foi encontrada alteração nas consultas oficiais dirigidas desta execução; isso não certifica inexistência de actos posteriores. A compilação de2025 não constitui consolidação universal de2026. Na ligação executável, uma fonte/versão sem cobertura temporal não autoriza fallback para a versão mais recente. Os artigos citados são do **Regulamento anexo** quando identificado “R”; “L” designa a Lei8/2023. Rastrear esta distinção evita confundir os dois artigos9 ou o artigo5 preambular com o do Regulamento.
|
||||
|
||||
| Condição | Fonte e período | Efeito no formulário | Efeito no percurso | Efeito no cálculo | Informação em falta / estado da ligação |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| Projecto económico em Moçambique que procura garantias/incentivos; actividade efectiva conhecida | L2(1)–(3), pp.8–9; período2026 deste lote | Reutilizar localização e actividade; perguntar a finalidade jurídica apenas se ausente | S02 identifica cobertura do procedimento; actividades excluídas recebem encaminhamento próprio | Não inferir benefício ou exclusão de IVA a partir deste âmbito | Falta ligação executável da cobertura ao perfil; legislação sectorial especial fica fora deste lote |
|
||||
| Capital/recursos do exterior ou nacionais; pessoa, residência e constituição separadas | L3/anexo, pp.28–29; R4–6, pp.38–40; período2026 | Reutilizar os dados declarados; pedir origem/forma do aporte quando faltar, sem transformar nacionalidade em classificação do capital | Acrescentar preparação da prova/registo do aporte externo quando aplicável | Nenhuma mudança automática de taxa IVA ou conversão; transferências cambiais não calculadas | Falta dado estruturado de aporte e ligação à documentação; requisitos cambiais concretos não abrangidos |
|
||||
| Implementadora em constituição ou já registada | R9(1)(b), p.41; R14(1)(b), p.45; desde7/03/2024, recorte2026 | Mostrar a alternativa documental correspondente, sem exigir novo preenchimento da entidade | S05/S06: reserva de denominação ou certidão comercial, com estado declarado/associado por conferir | Não altera IVA | Falta associar categorias documentais existentes à alternativa e ao estado do cadastro |
|
||||
| Implantação em instalações arrendadas ou com DUAT | R9(1)(c)–(d), pp.41–42; R14(1)(c)–(d), p.46; recorte2026 | Reutilizar local; perguntar apenas a modalidade/evidência ainda ausente | S06 identifica esboço/planta e alternativa contrato/DUAT; sem converter ausência de título em ausência de direito | Não atribui taxa, custo fundiário ou benefício | Falta modalidade estruturada e associação ao documento; sem decidir titularidade/disponibilidade |
|
||||
| Pedido por representação comercial estrangeira, distinto de qualquer investidor estrangeiro | R9(2)–(3), p.42; recorte2026 | Pergunta de representação apenas quando a implantação declarada o permita; depois referência da licença e documento | S06 acrescenta licença de representação e identifica formalidades documentais pertinentes | Sem alteração da taxa geral | Falta modalidade e origem documental estruturadas; não exigir licença a todo investidor estrangeiro |
|
||||
| Nenhuma condição de autorização; ou dimensão/actividade/terra que a exija | L22(1)(b)–(2), pp.20–21; R13(2), R14 e R21, pp.45–50; recorte2026 | Reutilizar montante/moeda/actividade/área; perguntar apenas PPP/concessão, implicações ou dados ainda desconhecidos | Seleccionar preparação de mero registo ou autorização; na segunda, S02/S06 acrescentam estudo de viabilidade e capacidade financeira | Comparação do montante exige MZN ou equivalência documentada; não converter nem inventar taxa; IVA permanece componente separada | Falta ligar os predicados e evidências ao perfil. “Não sei” não equivale a “não”. Limite de32mil milhões MZN só resolve a condição de montante, não todas as restantes |
|
||||
| Localização geral versus ZEE/ZFI com referência e âmbito documentados | R13(1)(a)–(b), p.45; R10(4), p.43; recorte2026 | Reutilizar zona/versionamento; pedir reconhecimento/condições pertinentes em falta | Diferenciar entidade de encaminhamento e articulação aplicável; S08/S17 conservam evidências e pendências | Sem regra executável do benefício, mostrar resultado especial indeterminado e cenário geral separado | a76369eb já apresenta referência/limite; faltam ligações jurídicas de encaminhamento e condições. Parque industrial não equivale automaticamente a ZEE/ZFI |
|
||||
| Operação mensal interna geral, base líquida, MZN e período coberto pela versão publicada | Regra IVA já fundamentada/autorizada; Lei22/2022 art.17(1), fontes/limites no recibo existente; recorte fiscal2026 | Reutilizar vendas/mês/moeda; perguntar apenas pressupostos fiscais ainda ausentes | Ligar cálculo e relatório à mesma versão de contexto/regra | Consumidor existente calcula por versão servida; zero difere de falta; preço com IVA incluído não vira base líquida | Ligação mensal já demonstrada. Dedução, reembolso, saldo final e benefício especial continuam fora |
|
||||
| Mudança de título/contacto ou outra condição irrelevante para a regra aplicada | Decisão Tempo22/10 e contratos existentes; escolha técnica de dependências identificada | Preservar a edição e preencher o que continuar compatível | Não reiniciar revisões/etapas sem dependência relevante | Não criar outra execução nem mudar taxa/base | Prova de título e testes de dependências já aceites; estender por dependência, não por “qualquer alteração” |
|
||||
|
||||
**Diferença útil que a implementação deste lote deve demonstrar, sem repetir A/B:** um caso C exclusivamente sintético, empresa em constituição, actividade económica não extractiva declarada, instalações arrendadas, capital emMZN, período2026. Primeiro completar as condições de enquadramento; depois a preparação de mero registo mostra a alternativa reserva de denominação/arrendamento. Alterar a condição de autorização para verdadeira troca o resultado procedimental, os campos necessários e os documentos para estudo/capacidade financeira. Com condição desconhecida, a selecção fica indeterminada e pede apenas essa informação. Alterar para representação comercial estrangeira acrescenta a licença pertinente; mudar apenas nacionalidade não produz esse efeito. Alterar de localização geral para ZEE/ZFI documentada altera o encaminhamento e as verificações, sem conceder benefício. O IVA geral continua matematicamente igual quando a mudança não afecta a sua regra; mudar vendas/base/moeda/período altera ou impede o cálculo conforme a cobertura existente. Não forçar um resultado numérico diferente quando a lei não o determina.
|
||||
|
||||
**Cadastro e código separados:** o conteúdo jurídico acima está preparado, com artigos, exemplar e início temporal. Falta cadastrar as correspondências versionadas campo/condição/etapa/documento e ligar a sua execução ao perfil existente. Usar o mesmo dossier, prévia/confirmar, definições/questionários versionados e suporte condicional `when` já utilizado nos formulários; reutilizar resolvedor temporal, avaliação e aritmética exacta nas interfaces em que sejam compatíveis, com adaptador explícito para os predicados procedimentais, sem criar outro motor nem armazenar procedimentos como imposto. O contexto já tem sector/actividade/localização/moeda/período/entidade; as respostas de modalidade e evidências ainda não ligadas devem ser acrescentadas ao mesmo contexto cifrado, com origem e alterações deliberadas. Reutilizar vínculos documentais/históricos e permissões existentes. Não alterar a definição publicada dos19passos nem activar outra regra fiscal para executar este cadastro.
|
||||
|
||||
**Estado desta entrega:** revisão de a76369eb concluída no âmbito aceite; **percurso seguinte fundamentado e delimitado, ainda não implementado/demonstrado de ponta a ponta**. A tabela não é prova de funcionalidade nem declara licenciamento sectorial completo. Não integrar este documento como se instalasse o percurso. Próxima execução técnica concreta: ligar as condições acima, persistir decisões/documentos e verificar apenas as transições de C, recuperação e relatório; conservar as provas aceites de contexto e IVA. Código, publicação de parâmetros/regras e activação continuam operações separadas. Não há nova autorização organizacional ou validação jurídica da Tempo presumida.
|
||||
|
||||
|
||||
### Percurso de investimento privado executável em QA — 22/10/2026
|
||||
|
||||
Implementação do delta de `52fbb146`, na branch `codex/private-investment-path-executable-QA`. A integração independente de `a76369eb` não foi repetida nem condicionada por este lote. Não houve escrita na homologação, integração em main, publicação ou activação de regras fiscais.
|
||||
|
||||
**Orientação permanente aplicada:** decisão expressa da Tempo de 22/10, registada também em AGENTS.md: legislação, modelos e procedimentos oficiais disponibilizados são a base aceite para implementar; decisões da Tempo orientam a implementação. Não existe nova aprovação jurídica genérica, aprovador adicional ou validação de credibilidade como condição para executar este percurso. Dados concretamente em falta são identificados por campo e não anulam os restantes requisitos. Associação documental não equivale a reconhecimento administrativo; esta distinção é informativa, sem bloquear a gravação ou a preparação.
|
||||
|
||||
**Ligação executável:** `PrivateInvestmentPreparation` é o adaptador delimitado das condições procedimentais já documentadas para o contexto cifrado do dossier. Correspondência `private-investment-preparation-2026-v1`, exemplar APIEX/hash anteriormente registados, recorte de início previsto em 2026. Reutiliza campos conhecidos do mesmo projecto/rascunho, permissões, vínculos documentais, prévia assinada, controlo de concorrência, chave idempotente, eventos antes/depois e os 19 passos existentes. Não é outro motor fiscal, catálogo paralelo de benefícios ou novo processo de aprovação. O resolvedor fiscal publicado e a aritmética decimal existentes continuam responsáveis pelos seus cálculos; não se armazenaram predicados procedimentais como impostos.
|
||||
|
||||
No dossier normal, «Preparar o pedido de investimento privado» abre o editor de contexto. Valores já declarados ficam recuperados e editáveis, sem nova confirmação por ecrã. Dados condicionais de área só aparecem para extensão de terra/concessão florestal. A prévia mostra ramo antes/depois, documentos novos/inactivos, passos afectados e resultados que precisam de revisão. Apenas a confirmação grava uma revisão; cancelamento conserva o estado anterior. Os requisitos correspondentes aparecem nas etapas S02/S05/S06 e o próximo passo acompanha mero registo/autorização. As dependências formais da definição publicada não foram substituídas por novos bloqueios.
|
||||
|
||||
**Fundamento executado:** Lei 8/2023 artigos 2 e 22; Regulamento aprovado pelo Decreto 8/2024 artigos 5, 9, 13, 14 e 21, conforme a tabela de 52fbb146 acima. Na interface, R designa este Regulamento. As exigências têm razão, artigo, período e ligação ao exemplar oficial. As alternativas certidão/reserva e DUAT/arrendamento não são cumulativas. Representação estrangeira determina a licença correspondente; nacionalidade ou residência, isoladamente, não a determinam. Aporte externo tem origem declarada separadamente da nacionalidade. Montante >32 mil milhões MZN, terra >=10 mil ha e concessão florestal >100 mil ha usam comparação decimal exacta, sem conversão cambial presumida. R21 distingue Finanças/Conselho de Ministros conforme as condições efectivamente conhecidas; uma condição positiva suficiente não espera pelas restantes para identificar o pedido de autorização.
|
||||
|
||||
| Comparação nova, exclusivamente QA | Campos e documentos | Próximo passo / efeito no cálculo | Prova |
|
||||
| --- | --- | --- | --- |
|
||||
| C: empresa em constituição, actividade geral, 1.000.000 MZN, 01/01/2026, modalidade doméstica, capital nacional, instalações arrendadas; condições de autorização negativas | Reserva de denominação, identificação, planta e arrendamento; não exige certidão, DUAT, licença de representação ou estudo/capacidade de autorização | Mero registo; encaminhamento Governador. Nenhuma taxa IVA alterada | UI → prévia → confirmar; contexto v1 |
|
||||
| C: associar suporte sintético já existente ao arrendamento | Mesmo documento associado por ID; não cria cópia nem o aprova | Conserva o pedido; contexto v2 | UI; um vínculo em desk_documents |
|
||||
| C: PPP/concessão empresarial passa a sim, modalidade passa a representação estrangeira e instalações a DUAT | Entram estudo/capacidade, licença de representação e DUAT; arrendamento deixa de ser exigido, conservando o documento anterior | Autorização, Conselho de Ministros; S02/S05/S06/S07 para revisão. Esta mudança meramente procedimental não invalida os inputs do IVA | Prévia visual antes de confirmar; contexto v3 recuperado; PNG C-authorization-preview |
|
||||
| C: recuperação e simulador | MZN, data, título, contexto v3 e preparação por autorização recuperados sem reintrodução; arrendamento inactivo visível no histórico | Mesmo âmbito fiscal geral; nenhuma execução fiscal nova pedida neste ensaio | Dossier normal, simulador e rascunho ligado; leitura persistida e relatório Word |
|
||||
| Regresso autorização → mero registo; entidade em constituição → existente | Estudo/capacidade/licença deixam de ser exigidos; arrendamento anterior volta associado; certidão substitui reserva, sem apagar histórico | Ramo e requisitos recalculados | Controlo HTTP transaccional dirigido, não repetição visual A/B |
|
||||
| ZEE/ZFI versus parque industrial | Encaminhamento APIEX condicionado ao enquadramento reconhecido; parque não é convertido em ZEE/ZFI | Nenhum benefício ou taxa especial concedidos | Predicados com referências sintéticas de QA; não prova de elegibilidade de uma zona real |
|
||||
|
||||
**Provas novas:** três testes feature, 96 asserções, exclusivamente na base `simulator_fiscal_delta_20261021_test`, loopback 55441, com transacções revertidas e guarda do alvo. Cobrem limiares exactos, desconhecidos, exclusões/período, não inferência por nacionalidade, alternativas e preservação de vínculo, recuperação, idempotência, prévia sem escrita, adulteração, documento de outro dossier recusado e consumidor desligado, incluindo edição independente do título sem perder snapshot ou vínculos. Um teste frontend confere somente o novo conteúdo congelado do relatório. TypeScript e build passaram. Não foram repetidos cálculos decimais, exportadores completos, paginação PDF, CSV ou ensaios A/B aceites.
|
||||
|
||||
**Recibo C da fase por autorização (histórico v3):** dossier `4133311b-5426-4cec-900d-1f1484484083`; contexto v3; rascunho `beba1dec-fc05-4820-b099-24952c5ab36c`, v4, SHA256 `4136e82a3b9420ab8f980ec0d7bda6dc2a67b568c18535b5ba0ecc80efd37e37`. Um documento sintético `e5ce142a-1178-46a2-b039-f187139c62cd`, sem pretensão de autenticidade oficial ou reconhecimento; mantém quarentena/scan pendente, sem contornar o carregamento/antimalware. A fixture apenas disponibiliza metadados privados para observar associação, não ensaia novamente uploads e não faz parte de dados/migrações de entrega. Três eventos de mudança do contexto; uma associação documental. C não contém projecção de vendas nem novo resultado fiscal; as provas anteriores dessa componente são reutilizadas.
|
||||
|
||||
**Relatório efectivamente transferido:** `relatorio-investmoz-sintetico-qa-c-pedido-de-investimento-privado.docx`, 16.674 bytes, SHA256 `28cfe4b75116c892d3e29fde389de7d067b456761756bab732af625178ee69ce`. Conferido o novo conteúdo: pedido de autorização, versão, fundamentos R14 e exigências não aplicáveis com histórico preservado. Não se repetiu a verificação de paginação/exportadores. Os anexos do dossier não são inseridos silenciosamente no pacote; mantém-se a selecção explícita existente. O relatório identifica a preparação/simulação e não alega submissão ou aceitação administrativa.
|
||||
|
||||
**Limitação da ferramenta:** a observação do evento de download excedeu o tempo e a extensão perdeu a ligação. O ficheiro terminou e foi encontrado em Transferências antes de qualquer repetição; conteúdo/hash conferidos localmente. Não se repetiu a exportação nem se classificou o incidente como falha do simulador. A prévia de impacto e recuperação já tinham prova visual; a recepção do ficheiro é prova distinta.
|
||||
|
||||
**Limites concretos:** este lote prepara o pedido de garantias/incentivos de investimento privado, não executa submissão administrativa, licenciamento completo por sector ou decisão de elegibilidade. Prospecção/pesquisa/produção extractiva, investimento público/exclusivamente social sem fins lucrativos e períodos fora de 2026 não são cobertos por este adaptador; processamento industrial tem o ramo de autorização documentado. A equivalência de montante noutra moeda não é determinada sem dados/fundamento cambial aplicáveis; condições positivas independentes continuam executáveis. O catálogo visual deste QA contém apenas parques sintéticos, não uma ZEE/ZFI real elegível: encaminhamento especial conferido no predicado, sem declarar prova visual de reconhecimento real. Benefícios territoriais, deduções, reembolsos e apuramento final do IVA continuam indisponíveis; o cálculo geral já autorizado conserva o seu âmbito.
|
||||
|
||||
**Utilização e integração:** abrir `http://127.0.0.1:18580/investimentos/4133311b-5426-4cec-900d-1f1484484083` na conta QA habitual. Usar «Comparar e alterar contexto», editar apenas a condição relevante, comparar e confirmar; para documentos, usar o Balcão do mesmo dossier e associar os existentes. QA habilita `investor-path.private_preparation=true` no router privado. A configuração de entrega `PRIVATE_INVESTMENT_PREPARATION_ENABLED` fica desligada por defeito; a homologação não foi alterada. Integração eventual pelo processo Git existente: rever branch/código e limites, incorporar sobre main actualizado preservando alterações concorrentes, build e conferir apenas o delta no destino; habilitação deste percurso é uma configuração separada, não publicação/activação fiscal. Suspender a configuração oculta o novo consumidor e recusa novas escritas específicas, sem eliminar contexto, documentos, eventos, rascunhos ou relatórios congelados. Sem migração ou seed de fixtures no destino. Nenhuma nova ronda de preparação técnica foi identificada para este recorte; revisão/decisão de integração continuam separadas.
|
||||
|
||||
|
||||
**Distinção final conferida, sem nova validação genérica:** Lei 8/2023 artigo 14(b) distingue investimento directo/indirecto; R13 refere expressamente investimento directo nos limiares monetários. Acrescentado o dado «Tipo deste investimento», separado da origem/nacionalidade. Sem esse dado, a via dependente da dimensão permanece indeterminada; para forma indirecta este lote não determina dimensão/encaminhamento monetário, mas continua a avaliar as condições independentes documentadas. PPP/concessão positiva continua a determinar autorização e Conselho de Ministros, sem esperar por esse campo irrelevante à decisão já suficiente. A interface foi conferida: retirar a condição PPP pede exactamente o tipo em falta; declarar directo resolve mero registo e retira estudo/capacidade financeira. Comparação antes de confirmar em `C-final-directness-preview.png` e percurso recuperado em `C-final-path.png`.
|
||||
|
||||
Estado final C: dossier v8, contexto **v4**, investimento directo declarado, PPP não, pedido de **mero registo**; quatro eventos de contexto e um documento/vínculo conservados. A modalidade estrangeira/DUAT conservou a edição deliberada. O rascunho v4 e o Word já transferido mantêm o contexto **v3 por autorização**, com hash acima: são prova histórica congelada e não o relatório da revisão v4. Não houve reescrita silenciosa nem reexportação. Ao abrir o simulador, o mecanismo comum de continuidade disponibiliza a revisão actual para o próximo rascunho/relatório; as provas anteriores deste mecanismo continuam reutilizadas.
|
||||
|
||||
|
||||
### Correspondência do novo relatório com o contexto v4 e dependências Git — 22/10/2026
|
||||
|
||||
Verificação dirigida solicitada pela Tempo após a avaliação do resumo de `a1fbda06`; nenhuma alteração funcional necessária. A prova anterior só demonstrava o Word congelado v3. Nesta continuação abriu-se o simulador pelo link normal do mesmo dossier C (incluindo o rascunho ligado), recuperou-se automaticamente o contexto v4 e gerou-se **uma** saída Word nova. Não se repetiram cálculos, CSV, paginação, anexos ou ensaios A/B aceites; não houve nova validação jurídica, integração ou alteração fiscal.
|
||||
|
||||
| Elemento | Word histórico v3 | Novo Word do contexto v4 |
|
||||
| --- | --- | --- |
|
||||
| Identificação dentro do documento | «Revisão 3» e «Contexto congelado v3» | «Revisão 4» e «Contexto congelado v4» |
|
||||
| Pedido | Autorização | Mero registo |
|
||||
| Estudo de viabilidade | Documento por associar | Não aplicável; histórico preservado |
|
||||
| Capacidade financeira | Documento por associar | Não aplicável; histórico preservado |
|
||||
| Bytes / SHA256 | 16.674 / `28cfe4b75116c892d3e29fde389de7d067b456761756bab732af625178ee69ce` | 16.678 / `a2849427f4a91fa6ab78e433be8b919baca80a0f4f452a4546a5b169b921d753` |
|
||||
| Cópia identificada para revisão | `outputs/private-path-executable-20261022/C-report-HISTORICO-contexto-v3.docx` | `outputs/private-path-executable-20261022/C-report-contexto-v4.docx` |
|
||||
|
||||
O original v3 conserva exactamente os bytes/hash anteriores; a cópia está explicitamente designada **HISTÓRICO**. A revisão congelada no próprio Word identifica o contexto a que pertence, sem o transformar no estado actual. O novo ficheiro foi encontrado em Transferências após o clique de exportação e conferido por leitura das células efectivamente geradas; recibo em `C-report-v3-v4-comparison.json`. O rascunho passou a v5, SHA256 `ba824fe0362ca87b301da55298122796f44fdfb11507830b3fe8f54cf88d132d`, com contexto v4 e preparação por mero registo; dossier v8/contexto v4 e um documento mantidos. Prova visual de recuperação em `C-new-report-context-v4.png`. Não se altera a validade histórica da prova v3.
|
||||
|
||||
**Dependências para integração após a avaliação da Tempo:** ancestralidade confirmada `57fc9148 → a76369eb → 52fbb146 → a1fbda06`. `a76369eb` fornece a continuidade comum do contexto/edições e é dependência funcional de `a1fbda06`; pode ser integrado independentemente. `52fbb146` é a preparação documental intermédia, sem código executável. A branch `codex/private-investment-path-executable-QA` inclui essa ancestralidade e a presente prova documental; não aplicar apenas o diff de `a1fbda06` num destino sem a continuidade ou alteração equivalente. Se a76369eb já tiver sido integrado, o merge normal reconhece a ancestralidade e não o reaplica como um segundo desenvolvimento.
|
||||
|
||||
Procedimento: após a decisão, recuperar o main actual, rever a comparação e fazer integração Git que preserve as alterações concorrentes; resolver apenas conflitos efectivos, sem reset/force-push de main. A leitura desta continuação encontrou main em `57fc914821d0cefa02caa80a74b3d42e663f2742`; é fotografia do estado, não autorização para ignorar commits posteriores. Conferir a revisão servida e só o delta no destino. A habilitação `PRIVATE_INVESTMENT_PREPARATION_ENABLED` é configuração distinta; não publica nem activa regras fiscais. Nesta execução a branch permanece separada, sem integração/habilitação na homologação. Limitações sectoriais/fiscais explícitas mantêm âmbito próprio e não bloqueiam este recorte concluído.
|
||||
|
||||
|
||||
### Integração autorizada do percurso adaptativo — 22/10/2026
|
||||
|
||||
A Tempo autorizou a implementação após o fecho do recorte. Main remoto foi actualizado por avanço directo de `57fc9148` para `064296e1`, preservando a sequência `a76369eb → 52fbb146 → a1fbda06 → 064296e1`; não havia alteração concorrente ou conflito no momento do envio. A cadeia automática recusou a primeira tentativa no teste frontend «server draft snapshots split private identity from economic values»: 188/189 passaram. A versão servida anterior `57fc9148` foi conservada, conforme o mecanismo de recuperação; não se contornou a verificação.
|
||||
|
||||
Causa concreta: o adaptador de continuidade acrescentava `compatible_fields_snapshot` vazio na recuperação de um rascunho legado sem nenhuma origem compatível. Correcção delimitada na branch `codex/adaptive-delivery-compatibility-QA`: devolver o rascunho original quando não há origem compatível nem metadados de herança anteriores. A herança com origem/metadados continua executada, preservando edições e limites por dossier. Nove controlos dirigidos da herança e o teste exacto da partição/recuperação passaram; não se reabriram os cálculos, relatórios ou cenários aceites. A correcção deve seguir o mesmo processo automático; não altera regras fiscais, esquema ou dados activos. Recibos da tentativa e da disponibilização final em `outputs/investmoz-adaptive-path-delivery-20261022`.
|
||||
|
||||
|
||||
### Percurso adaptativo disponibilizado na homologação — 22/10/2026, 10:17 Maputo
|
||||
|
||||
Autorização expressa da Tempo executada. Main passou de `57fc9148` por avanço directo para `064296e1`, conservando a ancestralidade `a76369eb → 52fbb146 → a1fbda06 → 064296e1`. O remoto não tinha alterações concorrentes adicionais no momento da integração; não houve conflito, reset ou envio forçado. A primeira verificação automática recusou o novo metadado vazio na recuperação legada e conservou a release anterior; a causa e a correcção dirigida constam da secção anterior. A correcção `042ffbde44cac27704d6f8b7ec25b525a42e3575` foi ensaiada em branch e integrada por avanço directo, sem suprimir o teste que detectou a incompatibilidade.
|
||||
|
||||
**Revisão efectivamente servida:** `042ffbde44cac27704d6f8b7ec25b525a42e3575`, branch main, build de 22/10/2026 às 10:16:14 Maputo; instalação automática concluída às 10:16:16. Manifesto SHA256 `655eef6e6a5e71c5138c68465a5b2ffc36d2cadeff96e6b35682dbfd19d42e79`. Conferida em `https://teste-investidor.tempo.co.mz/build/revision.json` e no recibo protegido da cadeia. A cadeia correu as verificações próprias; manualmente só foram repetidos os dez controlos afectados pela incompatibilidade. Zero migrations e zero importações no destino, sem copiar fixtures QA.
|
||||
|
||||
**Funcionalidade utilizável:** `https://teste-investidor.tempo.co.mz/investimentos` (login normal); abrir ou criar o dossier legítimo do investimento e usar «Preparar o pedido de investimento privado» / «Comparar e alterar contexto». Estão disponíveis as perguntas condicionais, fundamentos, comparação antes de confirmação, requisitos/documentos e próximos passos do lote `private-investment-preparation-2026-v1`, com os limites de 2026 já documentados. A conferência do destino limitou-se à revisão, configuração efectiva e entradas: investimentos redirecciona ao login (302), simulador responde 200. Não se criaram dossiers/contas reais ou sintéticas no destino nem se repetiram os cenários funcionais QA aceites.
|
||||
|
||||
**Habilitação concreta, distinta de fiscalidade:** a configuração do percurso `PRIVATE_INVESTMENT_PREPARATION_ENABLED` estava ausente/false; passou a true mediante a sessão SSH habitual autenticada pelo utilizador. Antes da escrita foram conferidos revisão servida e hash da configuração; cópia protegida dos bytes anteriores verificada, alteração limitada a essa chave, proprietário e modo0640 preservados, cache de configuração reconstruída e workers notificados de reinício gracioso. Hash .env anterior `7cdaba591bb5787485a349c24db2fd7c8279436da0eb31502e44db2fffa21d03`; posterior `a6b4e9064cf6dbb2d945b53418a6c9252f9c736ff3fde4016b95b74885a9ee0f`. A leitura posterior confirmou `investor-path.enabled=true` e `private_preparation=true`. A cópia de recuperação permanece privada em `/var/lib/investmoz-deploy/backups/private-preparation-20261022/`; não contém uma publicação de parâmetros e não segue para Git.
|
||||
|
||||
**Estado fiscal preservado:** nenhuma publicação/activação de nova regra e nenhuma operação na base activa. O consumidor IVA geral previamente autorizado manteve a mesma disponibilidade, cobertura2026 e mensagem antes/depois. As exclusões sectoriais, forma indirecta/dimensão monetária não determinada, benefícios territoriais e apuramento fiscal completo continuam explícitos e não bloqueiam o lote procedimental concluído.
|
||||
|
||||
**Suspensão/recuperação:** desligar somente a chave do novo percurso e reconstruir cache/reiniciar graciosamente os workers mantém dossiers, documentos, respostas, versões, relatórios e históricos. Para reverter configuração, usar o valor anterior apenas depois de conferir o hash actual e preservar alterações posteriores; não substituir todo o .env sem conferir concorrência. A reversão de código/build segue o procedimento de releases já existente, sem restaurar dados antigos. A release anterior foi efectivamente conservada na primeira tentativa recusada. Recibo completo local em `outputs/investmoz-adaptive-path-delivery-20261022/delivery-receipt.json`; a presente actualização documental fica na branch de registo, sem novo push main ou nova disponibilização desnecessária.
|
||||
|
||||
|
||||
## Revis?o das 30 fichas de Inhambane e cat?logo vazio ? 22/10/2026
|
||||
|
||||
Leitura remota autenticada, transac??o READ ONLY, base teste_investidor: 30 oportunidades em draft; zero publicadas em toda a base. As consultas independentes de Cabo Delgado e Inhambane e a consulta exacta Inhambane/Vilankulo devolveram zero sem falha de consulta. A alega??o de oportunidades publicadas de Cabo Delgado n?o se confirma neste destino; n?o inventar associa??es para preencher a lista.
|
||||
|
||||
As 30 localiza??es permanecem nos recursos privados opportunity_location_drafts, referidos no cadastro can?nico; os campos e v?nculos p?blicos ainda n?o s?o a projec??o dessas fichas. A inexist?ncia desses v?nculos p?blicos em rascunhos n?o prova perda da localiza??o. Na publica??o, projectar somente a abrang?ncia documentada e conferir a pesquisa territorial.
|
||||
|
||||
Decis?o Tempo nesta conversa: incompletude e autoriza??es necess?rias ? execu??o n?o s?o impedimentos autom?ticos ? divulga??o como oportunidade em prepara??o. Proposta: divulgar resumos das 30 fichas em prepara??o com fonte, edi??o, data-base e lacunas vis?veis; n?o declarar financiamento, terreno, licen?as, promotor autorizado ou execu??o confirmados. Valores hist?ricos, agregados e divergentes devem ser identificados sem somas indevidas. Nenhum impedimento concreto de divulga??o do resumo foi demonstrado nesta leitura. Isto n?o declara o conte?do privado integral public?vel: originais, contactos e evid?ncias continuam separados. N?o houve publica??o, altera??o de pap?is ou escrita remota.
|
||||
|
||||
Prepara??o por ficha: outputs/investmoz-territory-recovery-20261022/publication-review.json (30 identidades reais, localiza??o privada, lacunas e valores). Evid?ncia: read-catalogue-remote.py e remote-read.log no mesmo direct?rio. A tabela hist?rica de lacunas de prepara??o ? reutilizada, sem a converter em bloqueios legais. A prepara??o t?cnica do fluxo de publica??o ainda deve reconciliar essas condi??es com o contrato existente; n?o marcar publica??o como pronta ou executada.
|
||||
|
||||
O URL recebido ? do cat?logo/iniciar por territ?rio, n?o do dossier afectado. N?o permite ler o contexto inicial, a altera??o e a ?ltima ac??o desse processo. A falha da pr?via territorial e do t?rmino sem resposta continua aberta e ainda n?o foi reproduzida no dossier real; n?o encerrar pela disponibiliza??o anterior ou por esta leitura de cat?logo.
|
||||
<!-- search-territory-recovery-20261022 -->
|
||||
## Continuidade territorial da pesquisa — correcção em QA, 22/10/2026
|
||||
|
||||
Esclarecimento da Tempo: o percurso reportado foi navegação pública entre carteira e catálogo, sem clicar em iniciar/continuar ideia. Não existe evidência de alteração de um dossier pessoal neste caso. Retira-se a exigência de URL do dossier e de confirmação de impacto; a alteração é de filtros de pesquisa.
|
||||
|
||||
Causa verificada: ligações da carteira para oportunidades e zonas e ligações dos menus públicos usavam destinos sem província/distrito. O contexto transportado para detalhes por `catalogue_query` também não acompanhava as ligações cruzadas. No ecrã de zonas, os parâmetros de exportação/detalhe omitiam a hierarquia territorial.
|
||||
|
||||
Correcção: `searchTerritoryHref` transporta exclusivamente os quatro níveis territoriais na URL entre carteira, oportunidades, zonas e educação, incluindo os respectivos detalhes. Reutiliza o `catalogue_query` existente e preserva os filtros próprios do destino. Uma selecção explícita de outro ascendente não herda descendentes incompatíveis. Sem armazenamento global, mistura de contas ou alteração de dossiers. Menus, navegação relacionada, breadcrumbs e ligações cruzadas usam a mesma função. O selector partilhado mantém a limpeza dos descendentes; não há confirmação adicional para pesquisar.
|
||||
|
||||
Verificação dirigida: seis testes Node (incluindo o selector partilhado), TypeScript e build isolado. No navegador QA: carteira Cabo Delgado → selecção Inhambane/Vilankulo → pesquisar → catálogo com ambos os campos preenchidos; recarregamento e regresso preservam a selecção; zonas herdam os dois níveis; mudar para Cabo Delgado limpa Vilankulo. Contagem zero, filtros activos e mensagem de ausência coerentes na base QA consultada. Não foram repetidos cálculos ou exportadores fiscais nem publicados registos para produzir resultados artificiais.
|
||||
|
||||
Provas: `outputs/investmoz-territory-recovery-20261022/before-carteira.png` e `after-catalogue.png` no workspace de entrega; testes em `tests/frontend/search-territory.test.mjs` e `territory-path.test.mjs`; logs QA `outputs/territory-types.log` e `territory-build.log`. O router privado QA selecciona o build isolado; não integra a candidata.
|
||||
|
||||
Entrega limitada a branch/QA. A homologação não foi alterada por esta correcção. Publicação das 30 fichas de Inhambane permanece separada: revisão por ficha preparada, nenhuma publicação executada; ainda por reconciliar a preparação técnica da divulgação em preparação com o fluxo existente. A correcção da pesquisa não depende dessa publicação.
|
||||
## Inhambane — divulgação dos 30 resumos executada, 22/10/2026
|
||||
|
||||
Decisão expressa da Tempo na conversa: «publicar» os 30 resumos como oportunidades em preparação; responsável confirmado `nala@tempo.co.mz`. A conta concluiu autenticação normal com MFA. A primeira ligação abriu indevidamente `/management/opportunities`, que exige administração geral; `/minhas-oportunidades` estava acessível e foi confirmado no navegador. A ligação SSH estava pronta; não foi necessário pedir outra autenticação.
|
||||
|
||||
Executada uma operação privada de dados, limitada às 30 identidades e aos hashes das versões autorizadas, sob a decisão de divulgação da Tempo e a sessão real de Nala. Não se atribuiu papel geral de Publicador, administração, revisões técnicas, aprovação jurídica ou autorização de execução. O fluxo genérico de aprovação de projectos completos permaneceu intacto. A publicação promocional tem finalidade própria registada em `workflow_context.publication.purpose=promotional_preparation`; não equivale à conclusão desse fluxo. Cada operação registou responsável, conteúdo anterior, hash do lote e finalidade no histórico restrito.
|
||||
|
||||
O executor preserva o cadastro canónico privado, proprietário e organização, originais e contactos. A projecção pública contém título, resumo, limitações, fonte/edição e indicação «Em preparação». Montantes não confirmados não são apresentados como investimento disponível; os dados técnicos/financeiros históricos são assinalados como tal. A conferência editorial retirou instruções internas dos resumos, usando as limitações da tabela aprovada na conversa.
|
||||
|
||||
Detectado e resolvido o vínculo territorial em falta: as localizações privadas mencionavam Vilankulos, mas só tinham província estruturada. O cadastro público confirma MUNICÍPIO DE VILANKULO → posto VILANKULO → distrito VILANKULO → Inhambane. Foram associados ao distrito apenas 15 registos cuja localização documental menciona expressamente Vilankulos, incluindo programas de abrangência múltipla. Não se inferiu implantação, coordenadas ou relação por proximidade. As restantes fichas conservam a abrangência documentada; não foram associadas indiscriminadamente ao distrito.
|
||||
|
||||
Verificação pública sem sessão da aplicação: **30 resultados em Inhambane, 15 em Vilankulo**, todos com indicação «Em preparação», limitações e sem `management_draft`/`workflow_context` na resposta pública. No navegador foram conferidos catálogo, contagem e a ficha do Hotel Boutique no Terreno INATUR, incluindo a ausência de confirmação da disponibilidade do terreno/concessão. Foram reutilizadas as provas anteriores; o ensaio novo em base QA isolada conferiu publicação, alteração editorial, repetição sem duplicados, preservação do cadastro privado, recusa de alteração concorrente e associação exacta das 15 referências. Todos os dados sintéticos foram revertidos por rollback em QA; nenhum actor QA foi transportado.
|
||||
|
||||
Backup fresco em formato PostgreSQL custom antes de cada revisão de dados, protegido fora da raiz pública; catálogo de restauro verificado e provas anteriores de restauro reutilizadas. A leitura final confirmou as mesmas 30 identidades, cadastro privado/proprietários preservados, zero novos papéis Publicador e administração geral de Nala mantida desligada. A reversão deve partir do backup e do `before_data` restrito, comparar a versão actual e restaurar apenas as projecções/vínculos afectados, preservando edições posteriores; não restaurar toda a base sobre alterações concorrentes. Uma suspensão deve retirar os resumos do público, mantendo cadastro, relações e histórico, como operação expressamente registada; não apagar as fichas.
|
||||
|
||||
Recibos no workspace de entrega: `outputs/investmoz-territory-recovery-20261022/public-verification.json`, `integrity-receipt.log`, `publication-final-result.log`, `published-vilankulo-results.png`; executores privados e manifestos no mesmo directório, arquivados fora da raiz pública no destino. Hash final do lote: `d0a1146a7113aabafc3c2d08c0bdc17d12fc8a51606ba9ff7e79b154556d6e08`.
|
||||
|
||||
Revisão efectivamente servida: `042ffbde44cac27704d6f8b7ec25b525a42e3575`. Nenhuma instalação Git, migration ou activação fiscal foi executada nesta publicação. A candidata `9ac623a4` de continuidade dos filtros permanece separada de `main` e pronta para revisão de integração. A divulgação das 30 fichas deixa de estar pendente; os estudos, financiamento e autorizações de execução continuam como limitações explícitas das oportunidades.
|
||||
## Integração territorial e contacto comercial — 22/10/2026
|
||||
|
||||
Tempo autorizou integrar `9ac623a4` e pediu conferir a revisão servida. `57fc9148` é antepassado de `042ffbde`, e não uma revisão posterior: os hashes não têm ordem cronológica. A conferência remota comparou 685 ficheiros de aplicação/configuração com `042ffbde`, sem diferenças; o manifesto de build também coincide com o identificador publicado. O histórico de instalação confirma a passagem de `57fc9148` para `042ffbde`. Não se identificou reposição de código/build ou desaparecimento da entrega do simulador.
|
||||
|
||||
Integração Git por avanço directo até `1ec31856`, incluindo `9ac623a4` e recibos documentais. A primeira tentativa automática preservou a revisão anterior por falha Node: importação sem extensão em `catalogue-context.ts`. Corrigida a referência explícita `.ts`; passaram os dez testes dirigidos do módulo afectado e da continuidade territorial. Não se repetiram os percursos aceites nem a publicação de oportunidades.
|
||||
|
||||
Decisão expressa da Tempo: o rodapé deve apresentar **comercial@tempo.co.mz**, porque o contacto comercial com a instituição é feito por esse endereço. Actualizado o valor por omissão de `portal.institutional_contact_email`. Na homologação existia uma sobreposição explícita `PORTAL_INSTITUTIONAL_CONTACT_EMAIL=info@tempo.co.mz`: alterada exclusivamente essa chave para `comercial@tempo.co.mz`, com backup privado íntegro, propriedade/permissões preservadas e reconstrução da cache de configuração. A apresentação e o `mailto:` utilizam a mesma configuração existente; não houve envio de email.
|
||||
|
||||
Recibos de destino e integração: `outputs/investmoz-territory-recovery-20261022/revision-before-integration.log`, `footer-email-receipt.log` e recibo final de instalação no mesmo directório do workspace de entrega. A confirmação final deve identificar a revisão efectivamente instalada pelo processo automático, mantendo os 30 resumos e as regras fiscais existentes.
|
||||
## Selectores especializados do catálogo — QA, 22/10/2026
|
||||
|
||||
Pedido da Tempo: explorar estruturas públicas independentemente da existência de oportunidades, sem inventar associações nem divulgar registos privados. A homologação em `7b1c3579` já devolve e apresenta **Parque Industrial de Balama** e **Parque Industrial de Montepuez** no selector de Cabo Delgado; “Todos” é o valor inicial, não uma prova de ausência das opções. A API devolve duas zonas nessa província e oito no âmbito nacional. A base QA local tem outro conjunto de dados: não continha parques públicos ligados a essa província. Esta diferença de dados não é uma regressão demonstrada do selector da homologação.
|
||||
|
||||
Falhas verificadas no código: actividades e cadeias eram reduzidas às oportunidades publicadas encontradas no território/sector; estruturas públicas podiam depender da existência adicional de uma ficha publicada da carteira; os selectores vazios só apresentavam “Todos”; a consulta da ficha de um parque sem oportunidades não era apresentada no resultado vazio da grelha.
|
||||
|
||||
Correcção na branch `codex/catalogue-specialised-selectors-QA`: opções provenientes dos cadastros públicos existentes, preservando os critérios de consulta e os vínculos territoriais. Estruturas com território principal documentado não dependem de uma oportunidade nem de uma ficha adicional da carteira. Mantêm-se os vínculos territoriais já resolvidos pelas fichas publicadas. Cadeias acompanham os clusters públicos do âmbito seleccionado. Actividades são o catálogo público de classificação, explicitamente separado da disponibilidade local de investimentos; não se inventa uma associação territorial ou sectorial. Os oito selectores apresentam contagem ou explicação de ausência. Uma selecção indisponível permanece identificada, com possibilidade de remoção, em vez de parecer “Todos”.
|
||||
|
||||
Quando a estrutura seleccionada não tem correspondências, o resultado informa “Sem oportunidades publicadas associadas com estes filtros” e mantém ligação à ficha pública existente, em qualquer vista. Um registo mestre sem ficha pública detalhada é identificado como tal; não se cria uma ficha ou publicação automaticamente. A pesquisa continua a combinar todos os filtros, sem alargamento automático ou atribuição de benefícios. A alteração territorial mantém a limpeza dos níveis e selecções incompatíveis já implementada.
|
||||
|
||||
Provas novas, sem repetir os percursos anteriormente aceites: Cabo Delgado com dois parques sintéticos identificados; selecção de Balama, zero oportunidades, abertura da ficha, regresso e recarregamento com selecção conservada; limpeza; Nampula com parque sintético Topuito; ausência de restrição territorial com ambos os conjuntos disponíveis e selecção anterior limpa. Fixtures limitadas à base `simulator_fiscal_delta_20261021_test`, porta 55441, excluídas do Git e da disponibilização. Testes `CatalogueSelectorReferencesTest`: três testes, 27 verificações, transacções revertidas, incluindo exclusão de privados/outro território, catálogo de actividades independente dos resultados, estruturas/cadeia sem oportunidades ou fichas adicionais. `npm run types:check` e build QA passaram. Provas locais: `outputs/catalogue-selectors-20261022/audit.json`, `qa-balama.png`, `qa-nampula.png`, `qa-national.png` e logs de build/tipos no mesmo directório.
|
||||
|
||||
Diagnóstico separado do mapa: API pública de Cabo Delgado devolve território cadastrado, zero limites e nenhuma geometria acessível. Leitura SSH em transacção **READ ONLY** confirmou zero linhas em `geo_geometries` para essa província; neste caso não é uma camada existente ocultada pelo perfil. Não foram criadas coordenadas, geometrias, vínculos ou permissões. Este diagnóstico não equivale à implementação dos limites administrativos.
|
||||
|
||||
Integração pendente da revisão prevista: comparar com `origin/main`, preservar alterações concorrentes e integrar pelo processo Git automático existente. Não há migrations, publicação de oportunidades, activação fiscal ou transporte das fixtures. Conferir apenas o delta e a revisão servida depois de autorizada a integração; não repetir as provas já aceites.
|
||||
### Revisão dirigida da interacção do selector — 22/10/2026
|
||||
|
||||
Na candidata `1d921c9d`, ensaio pelo navegador Edge em QA, sem usar `selectOption` como prova desta interacção: clique no campo/seta deu foco; teclado `Alt+Down`, seta para cima e Enter mudou a selecção de Montepuez para Balama, actualizando o filtro e a ligação à ficha. Prova local `outputs/catalogue-selectors-20261022/qa-keyboard-balama.png`. Dados sintéticos explicitamente identificados como QA.
|
||||
|
||||
A ferramenta não observou o menu nativo aberto pelo rato; a tentativa de clicar na opção nativa devolveu opção não visível. Assim, a escolha completa **apenas pelo rato permanece não demonstrada** nesta revisão. Trata-se de limitação de observação/interacção da ferramenta, sem evidência suficiente para atribuir falha ao portal. A presença das opções na API/DOM e o uso anterior de `selectOption` não encerram esse ponto. Sem alteração funcional, repetição dos restantes testes ou integração em `main`; geometrias continuam como pendência distinta.
|
||||
### Decisão de integração — 22/10/2026
|
||||
|
||||
A Tempo encerrou a revisão de `1d921c9d` com as provas existentes e autorizou integrar a candidata e o registo documental `916819eb`. A interacção exclusivamente pelo rato permanece não observada pela ferramenta, **sem carácter bloqueante**; não constitui falha comprovada do portal, não exige substituir o selector nem repetir ensaios. A ausência de geometrias administrativas de Cabo Delgado permanece independente. Executar o processo Git automático existente, preservando alterações concorrentes; não transportar fixtures QA, publicar dados ou activar regras fiscais.
|
||||
|
||||
## Entrada única de gestão — preparação em QA, 22/10/2026
|
||||
|
||||
Origem: prioridade expressa da Tempo para a equipa cadastrar oportunidades e carregar, editar e publicar documentos e legislação pela interface, sem depender do Codex. A decisão de 22/10 sobre fontes e execução mantém-se. Geometrias administrativas ficam no saldo separado.
|
||||
|
||||
Implementada `/gestao-conteudos`, acessível pelo painel, menu lateral, Balcão e editores. Reutiliza os cadastros, biblioteca, armazenamento, perfis e histórico existentes. Nova oportunidade abre o editor operacional para quem tem contexto de criação; acesso legado é identificado como tal, sem o apresentar como publicação operacional completa. A entrada não atribui papéis. Escolhas técnicas deste delta: navegação agregada, contagens no âmbito do utilizador, pesquisa documental e pré-selecção do ficheiro acabado de carregar. Não são novas regras organizacionais.
|
||||
|
||||
Percurso diário: **Gestão de conteúdos e oportunidades → Nova oportunidade**, escolher promotor/modalidade, preencher e guardar; recuperar em **Gerir oportunidades e histórico**. Para documentos: **Novo documento** ou **Nova legislação → Receber documento em privado → Guardar em quarentena → Registar documento na biblioteca**. O original recém-carregado fica seleccionado; título e emitente conhecidos são recuperados. Completar os metadados, guardar, pesquisar o registo e usar **Editar metadados**, **Publicar** ou **Retirar da publicação**. Associações apresentam nomes dos registos existentes. Campos ainda não conhecidos não são inventados. Publicação para consulta não activa cálculos.
|
||||
|
||||
Verificação nova: quatro testes, 99 asserções, com transacções revertidas exclusivamente em `simulator_fiscal_delta_20261021_test`, loopback 55441, guarda explícita. Demonstrados acesso de equipa sem administração geral, recusa a utilizador sem autorização e ciclo documental completo (upload, ClamAV real, recuperação por pesquisa, edição, publicação/download, retirada, preservação do hash e quatro revisões). Exemplo documental exclusivamente sintético, sem origem oficial ou valor jurídico. Corrigida a classificação de retirados: o histórico existente usa `unpublished`; o filtro e o rótulo reconhecem também `withdrawn` histórico. TypeScript e build QA passaram.
|
||||
|
||||
No navegador, login e MFA normais da conta sintética de equipa; entrada → legislação; entrada → nova oportunidade → guardar preparação incompleta → recarregar, com título recuperado e ficha QA 241. Não foi repetido o workflow integral de revisão/publicação de oportunidades já existente; a prova nova cobre a entrada e a gravação/recuperação. O ensaio documental completo foi HTTP transaccional, não uma prova visual de cada clique. A conta QA tem autorizações explicitamente sintéticas; não comprova as designações das contas reais. Não transportar fixtures, configurações QA ou concessões de acesso.
|
||||
|
||||
Provas locais: `outputs/content-management-20261022/entrada-final.png`, `build-final.log`, e `tests/Feature/ContentManagementEntryTest.php`. Estado: candidata em branch `codex/content-management-entry-QA`, ainda sem integração ou instalação na homologação. Não declarar encerrada a autonomia operacional de todas as contas/categorias apenas por esta entrada. Não houve migration, publicação remota, alteração de papéis activos ou activação fiscal.
|
||||
|
||||
|
||||
## Recuperação da demarcação territorial — QA, 22/10/2026
|
||||
|
||||
Retomada expressa da Tempo como recuperação de funcionalidade anteriormente demonstrada, nos requisitos existentes AUD-GEO-02/03 e na experiência de cadastro. Não é criação de outra cartografia. O incremento de divulgação promocional permanece na sua branch independente.
|
||||
|
||||
**Causa comprovada:** leitura SSH autenticada, em transacção READ ONLY, da release `8286d750`/base `teste_investidor`: zero linhas em `geo_geometries`, incluindo eliminadas. Cadastro preservado: 11 províncias, 148 distritos, 437 postos e 1324 localidades. Directório dos originais privados ausente; importador desabilitado. Serviço de consulta, controlador e correspondências iguais por SHA256 ao código QA. Hash do cadastro (IDs, nomes, pais, nível, origem/códigos): `9de4181c42055bc6ef11b56333b64dd463d0ebe16bb2d1ea3238fc79e69024f7`, igual no QA e destino. Não se confirmou recusa de acesso nem perda de associação: faltam os próprios dados.
|
||||
|
||||
**Histórico:** as provas de 09–10/10 em `docs/investmoz/v2/auditoria-portal/correspondencias-cartograficas.md` registam 361→393→408 correspondências em bases QA anteriores. Os originais e recibos privados permanecem disponíveis. Os transportes de 19–20/10 deste manual excluíram expressamente geometrias; a disponibilização Git também exclui dados e ficheiros privados (`deploy/homologacao/README.md`). Em 22/10 a leitura de Cabo Delgado já encontrara zero linhas; esta passagem conferiu a tabela inteira. Prova-se uma diferença de composição entre QA cartográfico e homologação. Não foi localizado recibo da instalação anterior destes limites na mesma base; não se atribui data a uma suposta eliminação nem se descarta a observação anterior no mesmo endereço.
|
||||
|
||||
**Recuperação executada em QA:** o clone actual também tinha zero geometrias. Reutilizados seis originais INE/UNOCHA de 07/06/2019 (três GeoJSON e três metadados), hashes preservados e correspondências v2 existentes. O serviço `TerritoryGeometryImport` inseriu 11 províncias, 133 distritos e 264 postos, internos, com recibos e auditoria. Todos válidos/SRID4326. Nos exemplos, a consulta pública continua sem devolver limites internos. ClamAV real, assinaturas de 22/10: três originais limpos. ADM3, 26773796 bytes, excedeu o stream de 25 MiB e passou pelo scanner de ficheiro GIS com orçamento de 40 MiB nesse processo; tentativa inconclusiva preservada. Sem alterar limites globais ou configuração do daemon.
|
||||
|
||||
O mapa enquadra o limite seleccionado; se não existir, enquadra o nível superior disponível mais próximo e informa que é contexto. A lista distingue selecção de contexto superior. Fonte, edição e actualidade jurídica indeterminada preservadas. Limite administrativo não representa parcela nem autorização de investimento.
|
||||
|
||||
**Prova de interface:** oportunidade sintética QA268 criada no portal. Cabo Delgado → ANCUABE → posto ANCUABE → ANCUABE-SEDE; limites e aproximação progressiva nos três primeiros níveis. Recurso de contexto/principal guardado; após recarregar e abrir “Ver / editar / restaurar”, os quatro níveis foram recuperados. A localidade sem limite próprio apresenta aviso dos níveis superiores. A fixture QA255 anterior tinha versão fechada/payload sintético mínimo incompatível com gravação do contrato; não foi usada como prova de persistência nem corrigida silenciosamente. Uma navegação durante a recompilação QA conservava referência a asset removido; recarregar resolveu o carregamento, sem alteração no destino.
|
||||
|
||||
**Cobertura:** 408/581 elementos do exemplar aceite: 11/11 províncias, 133/159 distritos, 264/411 postos. Mantêm-se 26+147 correspondências pendentes, com nomes nas prévias e no documento de correspondências existente. O denominador do cadastro é diferente (148 distritos, 437 postos). Este exemplar não contém geometrias próprias de localidades ou parcelas; não atribuir limites superiores às 1324 localidades.
|
||||
|
||||
**Reposição preparada, não aplicada na homologação:** `outputs/investmoz-territory-recovery-20261022/demarcation/retained-geometry-restoration.zip`, SHA256 `53a5d8aa2be57603c928279cbd14561741dd2ec4b44b138fbc785380de6f951d`, contém seis originais e manifesto. Não contém dump de substituição, actores QA ou limites sintéticos. Procedimento existente:
|
||||
|
||||
1. Rever e integrar pelo Git o ajuste visual; conferir revisão efectivamente servida.
|
||||
2. Colocar originais no armazenamento privado partilhado, conservando hashes. Executar scanner actual ligado aos originais. Conferir backup/restauro e prova de activação da revisão/alvo, como exige o importador já existente; disponibilizar a configuração do importador.
|
||||
3. Gestor já autorizado, com MFA, abre `/management/territory-geometry-import`: prévia e aplicação de províncias, depois distritos, depois postos, com motivo/confirmar. Reconciliar prévia fresca no destino; não executar hashes antigos do QA. Recibos e reversão dos lotes novos existentes preservados.
|
||||
4. Conferir a cadeia territorial no formulário do destino e a recuperação após guardar, mantendo lacunas explícitas.
|
||||
|
||||
O staging dos originais, backup/restauro e configuração inicial ainda exigem operação técnica. Depois da reposição, seleccionar, guardar e recuperar a localização/mapa usam a interface. Esta passagem só leu o servidor remoto: sem alterar dados, permissões, camadas ou configuração. Não declarar a reposição no destino concluída.
|
||||
|
||||
Origem das restrições: decisões expressas da Tempo e requisitos anteriores do importador para preservação/backup e autenticação. Enquadramento e mensagens são escolhas de implementação, sem novo papel ou aprovador. Fonte oficial aceite reutilizada sem exigir nova validação da credibilidade.
|
||||
|
||||
Provas na pasta desta recuperação: inventário dos seis ficheiros, prévias/recibos por nível, `verification.json`, `homologation-readonly.json` e capturas. TypeScript, build QA e sintaxe PHP verificados; provas editoriais anteriores não repetidas.
|
||||
|
||||
|
||||
### Ajuste de acesso encontrado na disponibilização — 22/10/2026
|
||||
|
||||
A conta real de Superutilizador encontrou HTTP403 no importador: o middleware não incluía a cartografia no âmbito administrativo já aprovado e o serviço exigia exclusivamente o gate legado `manage-portal`. Corrigidos ambos para reutilizar `ScopedPortalAdministration`, com MFA de sessão e as exclusões existentes de Auditor. Não foram alteradas atribuições, papéis, contas ou o gate global. Origem do bloqueio: escolha antiga de implementação, não decisão organizacional da Tempo. Três testes transaccionais novos/15 asserções em QA: Superutilizador com MFA chega à validação de fonte/aplicação; sem MFA e conta comum são recusados; nenhum grant legado criado. Provas QA cartográficas aceites reutilizadas sem repetição.
|
||||
|
||||
|
||||
## Demarcação territorial reposta na homologação — 22/10/2026
|
||||
|
||||
Pedido expresso da Tempo executado. Candidata `15d82611` revista e integrada em `73c046e1`; ajuste de acesso `95d4cf44`, testado em QA, integrado em `30aed7cd`. A revisão `30aed7cd12ef65f797191467232087643c396751` foi instalada pelo processo Git existente e confirmada em `/build/revision.json?demarcation=30aed7cd`. O transporte de dados foi executado separadamente do Git.
|
||||
|
||||
### Dados, backup e importação
|
||||
|
||||
Seis originais INE/UNOCHA de 07/06/2019 transportados para o armazenamento privado partilhado: hashes iguais ao manifesto aceite, ficheiros 0640 e directório 0750, fora da árvore pública. Análise real dos seis pelo ClamAV 1.4.3, assinaturas de 22/10/2026, resultado limpo. Utilizado o protocolo local FILDES do daemon existente, sem instalar scanner, alargar permissões dos originais ou alterar limites globais: [documentação oficial ClamAV](https://docs.clamav.net/manual/Usage/ClamdProtocol.html).
|
||||
|
||||
Backup completo de `teste_investidor`, snapshot exportado consistente, restaurado em `127.0.0.1:55441/demarcation_hml_restore_20261022_complete_test`. As 171 tabelas coincidem integralmente por contagem e checksum canónico; arquivo dos 333 ficheiros verificado por conteúdo/hash. Recibo remoto em `/var/backups/investmoz-demarcation-20261022-complete/backup-receipt.json`, dump SHA256 `f80551de827e954a35b66ee4c8f2fcc559a4c88b48b3dc799c33b74907079c9b`. A comparação inicial revelou diferenças de ordenação Linux/Windows e a visão RLS reduzida da conta da aplicação; a comparação final usou ordenação canónica e a mesma visão completa do backup, preservando também os registos fiscais. Não se desactivou RLS.
|
||||
|
||||
A conta real `nala@tempo.co.mz` (ID15), com login e MFA normais renovados pelo titular, abriu as prévias frescas e aplicou os três níveis pela interface `/management/territory-geometry-import`:
|
||||
|
||||
| Nível | Criados | Pendentes | Lote auditado |
|
||||
| --- | ---: | ---: | --- |
|
||||
| Províncias | 11 | 0 | ec2c7c27-e4a3-466e-91a4-60aba939d3a0 |
|
||||
| Distritos | 133 | 26 | 142667c7-7309-4461-8d0a-fd00cb421577 |
|
||||
| Postos administrativos | 264 | 147 | b6d5a7e2-2f44-4bd6-9634-d97ee58ab718 |
|
||||
|
||||
Total: **408 limites internos**, válidos, não vazios, SRID4326; três recibos e três eventos `geometry_import_applied` ligados ao actor real. As geometrias foram repostas para consulta autorizada no cadastro; não foram publicadas camadas internas. Reversão existente disponível por lote, preservando originais, histórico e reutilizações e recusando alterações posteriores. Não se executou reversão no destino.
|
||||
|
||||
### Delta e acessos conferidos
|
||||
|
||||
Na oportunidade real #30, «Centro de Emergência Turística e Medicina de Mergulho de Tofo», a sessão real abriu Recursos → Ver recurso. O mapa desenha o limite da **província de Inhambane**, com fonte/edição e identificação de território seleccionado. Não é um polígono próprio de Tofo: distrito/posto/localidade continuam por indicar nessa ficha, como antes. A oportunidade, o recurso e a publicação não foram gravados nem alterados. Captura: `hml-demarcacao-tofo.png`. A prova QA aceite de guardar/reabrir quatro níveis foi reutilizada, sem repetição.
|
||||
|
||||
Consultas de controlo no destino recuperaram 1/2/3 limites na cadeia Cabo Delgado → distrito Ancuabe → posto Ancuabe. A localidade Ancuabe-Sede continua sem limite próprio e com três limites ancestrais de contexto. O cadastro territorial conserva exactamente o hash anterior `9de4181c42055bc6ef11b56333b64dd463d0ebe16bb2d1ea3238fc79e69024f7`.
|
||||
|
||||
Conferidos os acessos das contas existentes: IDs15 e16 mantêm a designação aprovada de Superutilizador; ID15 mantém Promotor e Validador. Não foram criadas contas ou atribuições. O endpoint do cadastro aceita os perfis já autorizados; a demonstração visual autenticada foi com ID15. A conferência das atribuições de ID16 foi de política, sem simular outro login. Quinze contas sem o perfil correspondente não satisfazem essa autorização. Pedido HTTP sem sessão ao endpoint privado devolveu 401; API pública devolveu zero limites internos; tentativa de obter o original pelo caminho público devolveu 403. Originais privados e regras de acesso preservados. Contas ainda não criadas não bloquearam a reposição.
|
||||
|
||||
O importador tinha um bloqueio legado de implementação: exigia `manage-portal` e não reconhecia a administração já aprovada. Corrigido por reutilização de `ScopedPortalAdministration`, com MFA de sessão e exclusões de Auditor existentes. Três testes novos/15 asserções passaram; não foi criado novo papel, aprovador ou gate global. Não houve repetição das provas QA cartográficas aceites.
|
||||
|
||||
Após a importação, **166/171 tabelas integralmente iguais** ao backup. Apenas `geo_geometries` (0→408), `geo_write_receipts` (0→3), `audit_events` (+6), `cache` e `sessions` diferem. Oportunidades, documentos, cadastro territorial, atribuições e par de Superutilizadores preservados integralmente.
|
||||
|
||||
### Guia curto e estado final
|
||||
|
||||
Entrar em [Gestão de conteúdos e oportunidades](https://teste-investidor.tempo.co.mz/gestao-conteudos) → Gerir oportunidades e histórico → abrir a ficha → Imagens, documentos e localizações → abrir ou preparar a localização. Seleccionar a hierarquia documentada; o mapa enquadra o limite disponível. Se faltar geometria própria, a mensagem distingue o contexto superior. Guardar o recurso e reabrir pela mesma lista recupera a preparação; não é necessário comando ou Codex para a consulta ou cadastro corrente.
|
||||
|
||||
**Reposição concluída neste recorte.** Mantidas as 26 correspondências de distritos e 147 de postos pendentes, com nomes nos documentos/prévias existentes. O exemplar não contém polígonos próprios das localidades ou parcelas. Essas lacunas não bloqueiam os 408 limites recuperados. A data de eventual desaparecimento anterior no mesmo endereço continua não determinada. A divulgação promocional mantém o seu incremento separado; este fecho não a declara integrada.
|
||||
|
||||
Provas: `homologation-restored-readonly.json`, `staging-receipt.json`, `preservation-receipt.json`, `http-access-verification.json`, captura e recibos locais na pasta desta recuperação; backup/restauro e prova de activação em caminhos privados. Uma interrupção do transporte local foi resolvida por escrita atómica da mensagem, antes de qualquer importação. Não ficaram passos técnicos pendentes para esta reposição; futuras importações usam as salvaguardas de backup/MFA já existentes.
|
||||
|
||||
## Concursos e anúncios — implementação em QA, 22/10/2026
|
||||
|
||||
Origem: pedido expresso da Tempo no anexo «O InvesteMoz deve ter uma secção pública Concursos e anúncios», com continuação autónoma autorizada. Aplicam-se as decisões permanentes da Tempo de 12/10 e 22/10/2026. Este incremento trata a divulgação de procedimentos; não abre recepção electrónica de propostas nem altera os investimentos associados. A estimativa apresentada antes da implementação foi de 16–24 horas; não representa tempo efectivamente consumido.
|
||||
|
||||
Reutilizados: entrada `/gestao-conteudos`, autenticação/MFA, autorização editorial e documental, entidades, oportunidades, projectos, sectores, território, biblioteca, originais privados, análise ClamAV, download verificado e `audit_events`. Acrescentados apenas três cadastros: procedimentos com rascunho, versões/actos imutáveis e acompanhamento individual. Uma oportunidade/projecto pode ter vários procedimentos; cada um admite lotes e actos associados. Não foram criados papéis, designações de funcionários ou aprovadores. Contas ainda não criadas não bloqueiam as contas já autorizadas.
|
||||
|
||||
### Fontes, âmbito e escolhas de implementação
|
||||
|
||||
- A UFSA identifica o Decreto n.º 79/2022 na [página oficial do regulamento](https://www.ufsa.gov.mz/divnovoreg.php). O [quadro oficial do Banco Mundial](https://www.worldbank.org/ext/en/what-we-do/project-procurement/framework) disponibiliza regulamentos e documentos normalizados, incluindo a edição de Setembro/2025. Cada procedimento regista a sua edição e acordo aplicáveis; a edição mais recente não é imposta a acordos anteriores.
|
||||
- Inventário deste recorte: instrução anexada, páginas oficiais referidas, pesquisa dos ficheiros acessíveis do repositório e referências da biblioteca existentes. Não foi identificado neste recorte um modelo oficial de concurso já fornecido como original local utilizável. Não se declarou importado ou analisado o articulado integral/modelos que não foram obtidos. Os documentos de demonstração são explicitamente sintéticos, sem classificação de autenticidade oficial. Os modelos disponibilizados pela Tempo usam a biblioteca existente, sem segunda validação genérica da credibilidade de fontes oficiais aceites.
|
||||
- Decisão expressa da Tempo: separar regimes Estado/externo/privado/outro; não generalizar UGEA, Cadastro Único, garantia ou abertura pública; guardar preparação incompleta; distinguir publicação de origem e publicação do portal; preservar actos, privacidade e auditoria.
|
||||
- Escolhas técnicas: só a prévia de publicação exige identificação, entidade, objecto, modalidade, instrumento/edição/fonte, calendário coerente, canal e motivo público. Rascunhos permanecem incompletos. Prazos mínimos são opcionais, citados por fonte/edição/modalidade; dias úteis usam exclusivamente o calendário documentado no registo. Não foi parametrizada uma tabela universal de mínimos legais nem presumido um calendário de feriados.
|
||||
- Escolhas técnicas de preservação: concorrência por versão, prévia assinada pelo servidor, nova verificação dos anexos na publicação, documentos por ID/hash e histórico imutável. Retirar ou substituir um binário na biblioteca torna indisponível o anexo antigo, sem apontar silenciosamente para outros bytes. Contacto sem autorização, notas internas e anexos privados não integram o conteúdo público. Ligações a oportunidades/projectos privados não divulgam os seus títulos ou fichas.
|
||||
- Autorizações reutilizadas: edição/publicação por contas já elegíveis para gestão documental completa ou edição editorial; auditor e conta simples não obtêm escrita. Acompanhamento exige apenas autenticação, incluindo conta sem email verificado. Estas escolhas não atribuem novos papéis e não dispensam os requisitos existentes de cada autorização.
|
||||
|
||||
### Guia curto da equipa
|
||||
|
||||
**Entrada QA:** [Gestão de conteúdos e oportunidades](http://127.0.0.1:18580/gestao-conteudos). Abrir **Novo concurso/anúncio**; escolher a entidade já cadastrada e, quando aplicável, oportunidade/projecto, sector e território. Indicar o regime e a edição do instrumento aplicável. Guardar mesmo com preparação incompleta; recuperar em **Gerir concursos e anúncios**. O motivo interno fica no histórico de gestão.
|
||||
|
||||
1. Preencher objecto, lotes, participação, financiamento, calendário, fuso horário e canal efectivo de apresentação. A data de origem é a referência do procedimento; o portal regista separadamente a sua publicação.
|
||||
2. Usar **Carregar / gerir documentos e scan na biblioteca**, que abre outra página sem perder o editor. Carregar o original, acompanhar o scan, editar os metadados e disponibilizar explicitamente os documentos autorizados. Voltar ao concurso, usar **Actualizar op??es da biblioteca** e seleccionar os anexos. Marcar públicos só os destinados à divulgação; os restantes permanecem privados.
|
||||
3. Guardar, escolher **Anúncio inicial** e **Preparar prévia pública**. Rever contacto, documentos, objecto, entidade, prazo e canal. O motivo público aparece também no histórico público. Usar **Publicar este acto**; alterações posteriores no rascunho não modificam essa publicação.
|
||||
4. Para esclarecer ou rectificar, guardar a revisão e publicar o acto correspondente. Para prorrogar, indicar o novo prazo, guardar e escolher **Prorrogação**; a prévia mostra o anterior e o novo. Resultados/adjudicação ou cancelamento usam o mesmo percurso, com texto e motivo documentados. O fim do prazo muda a indicação para **Prazo de propostas terminado**, sem adjudicação automática. Actos informativos posteriores não reabrem um cancelamento/resultado.
|
||||
5. Consultar o [catálogo público QA](http://127.0.0.1:18580/concursos); pesquisar por palavras, entidade, sector, localização, modalidade, financiador, regime, estado ou prazo. Abrir a ficha e **Voltar aos resultados** conserva os filtros. **Relatório imprimível → Imprimir anúncio** permite imprimir/guardar pelo navegador, incluindo o histórico dos actos.
|
||||
6. Uma conta simples usa **Acompanhar concurso** e **Os concursos que acompanho**; a lista identifica novos actos desde o registo de acompanhamento. **Deixar de acompanhar** remove só a subscrição individual. Não há promessa de alerta por email nem entrega de proposta: apresentar a proposta no canal indicado no anúncio/documentos.
|
||||
|
||||
Publicação documental disponibiliza um original da biblioteca; publicação de oportunidade disponibiliza a ficha pelas etapas existentes; divulgação promocional divulga o resumo autorizado de preparação; publicação de concurso divulga um procedimento e os seus actos. Nenhuma dessas operações autoriza execução do investimento, activa cálculo fiscal ou comprova envio à UFSA/financiador.
|
||||
|
||||
### Evidência e limites de entrega
|
||||
|
||||
Base exclusivamente QA `simulator_fiscal_delta_20261021_test`, porta 55441, com guarda de identidade antes de migração/fixtures/testes. Testes transaccionais preservam a base existente. `ProcurementTest` cobre preparação incompleta, concorrência, três regimes, filtros e retorno, prévia desactualizada, publicação anónima, prorrogação, expiração sem adjudicação, resultados, cancelamento persistente, conta simples, recusas de escrita, scan real, anexo privado, download anónimo e retirada documental. Ver resultado final em `outputs/procurement-20261022/tests.log`. Navegação existente: seis testes aprovados; compilação QA e TypeScript aprovados.
|
||||
|
||||
Exemplos persistentes, todos **SINTÉTICO QA**, sem concursos reais: estatal `17f44d8d-d077-4dca-9a63-5eb1c85a6926` (inicial + prorrogação, prazo de 20/11 15h para 16/12 17h, Africa/Maputo); externo `e85861cd-ffbb-4348-9cce-d588a6e09b86` (aberto); privado `fe271b02-0bfd-4fe2-8db2-856567b5111f` (inicial + cancelamento). Os rascunhos foram preparados por fixture identificada; guardar/reabrir, prévias e publicações destes actos foram executados no navegador pela conta sintética existente, não por comando de publicação. Resultado/adjudicação e expiração foram demonstrados pelos testes HTTP, não por adjudicação de um registo real.
|
||||
|
||||
Consulta anónima confirmada depois de terminar sessão: três regimes/estados no catálogo, ficha prorrogada, filtros preservados, relatório e download real do anexo 38. Anexo 39 e contacto sem autorização ficam privados. Acompanhamento confirmado pela conta simples já existente, sem MFA ou papel editorial. Evidências locais: `management.png`, `catalogue.png`, `print-notice.png`, `watch.png`, `fixtures.json` e resultados em `outputs/procurement-20261022/`. Estes artefactos QA não contêm credenciais na entrega Git.
|
||||
|
||||
Operações correntes deste incremento são feitas pela interface: cadastrar, guardar, recuperar, carregar/editar documentos pela biblioteca, pré-visualizar, publicar, actualizar/prorrogar, publicar resultado/cancelamento, consultar/imprimir e acompanhar. Não requerem executor privado ou Codex por concurso. **Pendente técnico de disponibilização fora de QA:** revisão e integração Git, migração pelo mecanismo de entrega e activação de `PROCUREMENT_ENABLED` no destino autorizado. O código usa flag desligada por defeito. Nenhum anúncio real foi publicado, nenhuma base activa foi migrada e não há integração automática com UFSA/financiadores. Geometrias não foram alteradas.
|
||||
|
||||
### Proposta de instalação compatível com deploy automático — candidata `1bcf34c0`
|
||||
|
||||
**Estado: proposta para revisão da Tempo, sem execução no destino.** O guia e os links QA da secção anterior foram conferidos pelo executor; a Tempo informou que ainda não os conferiu. A evidência QA aceite não equivale a aceitação pela Tempo nem a disponibilização na homologação. Destino previsto: `https://teste-investidor.tempo.co.mz`; confirmar a configuração instalada antes de executar. Esta passagem não integra em `main`, não altera configuração remota e não publica anúncios.
|
||||
|
||||
**Dependência de entrega encontrada.** Em `deploy/homologacao/pipeline.py`, `source_contract()` calcula SHA-256 dos ficheiros de migrations com normalização CRLF→LF; `build()` compara o conjunto completo com `schema_contract` e recusa qualquer diferença antes do build. O pipeline não executa migrations de produção nem importa conteúdos. A promoção troca atomicamente a release, verifica a revisão servida e a página inicial e reinicia a fila; estas verificações gerais não provam o acesso ao módulo. A configuração de exemplo aponta para `/etc/investmoz-deploy/schema-contract.json` e `/etc/investmoz-deploy/delivery.json`. Os ficheiros instalados podem divergir do repositório e devem ser lidos e comparados. Não se deve remover o gate nem converter o deploy em migração global automática.
|
||||
|
||||
**Delta exacto de schema.** Uma única migration: `database/migrations/2026_10_22_230000_create_portal_tenders.php`. Não altera tabelas existentes, extensões, utilizadores, papéis, grants, documentos ou dados territoriais; não contém seeds nem publicação de conteúdo.
|
||||
|
||||
| Tabela nova | Colunas | Chaves e relações efectivamente declaradas |
|
||||
|---|---|---|
|
||||
| `portal_tenders` | `id` UUID; `draft` JSONB obrigatório; `version` integer com default 1; `published_version_id` UUID anulável; `created_by` bigint obrigatório; `created_at`, `updated_at` timestamptz anuláveis | PK `id`; FK `created_by → users.id`, `RESTRICT` na eliminação |
|
||||
| `portal_tender_versions` | `id` UUID; `tender_id` UUID; `version` integer; `act` varchar(24); `payload` JSONB; `reason` text; `actor_id` bigint; `published_at` timestamptz anulável; `created_at` timestamptz obrigatório com default actual | PK `id`; UNIQUE (`tender_id`,`version`); FK `tender_id → portal_tenders.id` e `actor_id → users.id`, ambas `RESTRICT` na eliminação |
|
||||
| `portal_tender_watches` | `tender_id` UUID; `user_id` bigint; `seen_version_id` UUID anulável; `created_at`, `updated_at` timestamptz anuláveis | PK (`tender_id`,`user_id`); FK para `portal_tenders.id` e `users.id`, ambas `RESTRICT` na eliminação |
|
||||
|
||||
`published_version_id` e `seen_version_id` não têm FK nesta candidata; a associação de publicação é verificada pela aplicação, incluindo pertença da versão ao concurso. O histórico é imutável pelas operações da aplicação; esta migration não acrescenta trigger de imutabilidade. Índices novos: os das três PK e do UNIQUE indicado. O executor Laravel regista também a migration na tabela `migrations` existente. Confirmar tipos e restrições no catálogo PostgreSQL após instalação, não apenas a existência de uma tabela.
|
||||
|
||||
Dependências já existentes: PostgreSQL/JSONB, `users`, entidades `organizations`, `opportunities`, `projects`, `sectors`, `geo_locations`, biblioteca `portal_resources`/`documents` e relações documentais, `audit_events`, autenticação/verificação/MFA e autorização documental/editorial conforme a conta. A FK de instalação depende directamente de `users`; as restantes tabelas são dependências de execução do cadastro, prévia, consulta ou publicação. Manter os originais no armazenamento persistente privado e o scanner/fila da biblioteca operacionais; a publicação de anexos exige o scan e os direitos já aprovados. Sem nova dependência Composer/NPM ou serviço externo. `composer.lock` e `package-lock.json` não mudam. As provas locais de `ProcurementTest` usam a base QA nominal e ClamAV real; não adicionar esse teste ao CI remoto sem adaptar a sua guarda de ambiente e disponibilidade do scanner. O pipeline conserva a sua suite isolada existente; não se repete a bateria QA aceite.
|
||||
|
||||
**Sequência proposta, com pontos de paragem verificáveis:**
|
||||
|
||||
1. **Revisão e identificação.** Rever `1bcf34c0` e este plano. Confirmar SHA integral da fonte, revisão/release actualmente servida, configuração real do pipeline, contrato actual, ligação à base e estado da migration. Conferir as contas reais já existentes contra `DocumentManagementAccess`/`Procurement::access`: gestão completa ou designação editorial existente, email verificado e requisitos MFA aplicáveis; auditor e conta simples sem escrita. Não inferir acesso por nome do papel nem criar designações. Se uma conta ainda não existe, registar só essa pendência e continuar com as já autorizadas.
|
||||
2. **Coordenar a janela antes do merge.** Pausar o observador `teste-investidor-deploy.path` e impedir novo processamento durante a operação; aguardar o serviço em curso e coordenar com `delivery.lock`. Preservar os pedidos em fila e reconciliar qualquer integração concorrente antes de retomar. Não parar à força um build nem manipular a release servida. Confirmar `PROCUREMENT_ENABLED=false` na configuração efectiva; não colocar esta variável no ficheiro de flags CI, cujo validador só aceita prefixos `PORTAL_`/`INVESTMOZ_`.
|
||||
3. **Backup recuperável.** Guardar dump consistente da base de destino, cópia/snapshot do armazenamento persistente privado e público e manifesto de hashes, revisão/release anterior, contrato, configuração e recibos. Configuração e segredos ficam em local privado. Ensaiar o restauro desse backup numa instância isolada, sem substituir a base activa nem publicar ficheiros; conferir integridade, tabelas e amostra de binários. A prova de restauro antiga não substitui o backup desta janela. Registar hora/identidade da base e preservar os recibos.
|
||||
4. **Instalar schema sem activar.** Preparar a fonte exacta revista fora da árvore servida, sem fixtures QA, com dependências do lock e ligação autenticada ao destino conferida. Pela sessão técnica autenticada e com os privilégios de instalação existentes, executar **apenas** `php artisan migrate --path=database/migrations/2026_10_22_230000_create_portal_tenders.php --force` nessa fonte/configuração. Não executar `migrate` global, `migrate:fresh`, seeders ou scripts de demonstração. Se a migration já estiver aplicada, conferir o schema e não duplicar a instalação; tabelas parciais ou divergentes exigem reconciliação concreta, não DROP/recriação. Confirmar registo da migration e as três tabelas vazias, incluindo versões e acompanhamento. A release antiga continua compatível com estas tabelas adicionais.
|
||||
5. **Actualizar o contrato de migrations.** Guardar o contrato anterior e acrescentar exclusivamente o caminho/hash normalizado desta migration, após a instalação conferida. Comparar o contrato completo com a fonte revista: qualquer outra diferença deve ser explicada antes de prosseguir. Não regenerar/aprovar cegamente um contrato para o HEAD mais recente. Este passo é uma operação técnica explícita e não corre automaticamente por Git.
|
||||
6. **Integrar e transportar código, ainda desligado.** Integrar pelo processo Git após a revisão, identificar o SHA resultante em `main` e retomar o pipeline/pedidos preservados. Conferir que o contrato corresponde exactamente à fonte resultante; só então o build pode passar. Usar o build isolado e a promoção atómica existentes, sem editar ficheiros da release servida. Confirmar recibo, `/build/revision.json`, página inicial, biblioteca e gestão única. O módulo permanece desligado e as tabelas vazias; a existência do código não constitui activação.
|
||||
7. **Activar separadamente.** Com código/schema compatíveis conferidos, definir `PROCUREMENT_ENABLED=true` na configuração persistente do destino por acção técnica autenticada. Actualizar a cache efectiva de configuração e recarregar processos persistentes conforme a configuração instalada. O pipeline versionado não faz `config:cache` para a configuração real: não presumir que editar `.env` basta quando houver cache. Se se gerarem caches dentro de uma release sujeita a `tree_hashes`, reconciliar os hashes pelo mecanismo de entrega antes de novo deploy/rollback, sem mascarar outras diferenças. Não executar testes com `RefreshDatabase` no destino.
|
||||
8. **Conferir só o delta no destino.** Aplicar a lista abaixo, registar revisão, URLs, resultados e actor autenticado sem credenciais. Entregar o link único `https://teste-investidor.tempo.co.mz/gestao-conteudos` e o guia desta secção com os links QA substituídos pelos correspondentes do destino. Não declarar disponibilização enquanto esta passagem não estiver concluída.
|
||||
|
||||
**Verificação no destino sem publicar anúncios reais como efeito da instalação:**
|
||||
|
||||
- Sem sessão: `/concursos` responde 200, incluindo catálogo vazio quando não houver anúncios; navegação pública mostra a secção; não surge autenticação HTTP adicional. UUID de rascunho/inexistente não permite obter ficha ou relatório públicos; anexos privados continuam inacessíveis.
|
||||
- Com conta real já autorizada: gestão única mostra **Novo concurso/anúncio** e **Gerir concursos e anúncios**; abrir formulário, guardar um rascunho de verificação claramente identificado, recuperar e pré-visualizar, sem publicar. Registar ID e preservar o rascunho/histórico; não o converter num anúncio real. Conferir scanner/biblioteca existentes por diagnóstico e estado, reutilizando as provas de upload aceites.
|
||||
- Conta simples: pode abrir a consulta e área de acompanhamento, mas recebe recusa na escrita/gestão; auditor não recebe escrita. Utilizador sem sessão é encaminhado para autenticação nas rotas de gestão. Não alargar grants para passar a prova.
|
||||
- Se já houver anúncio autorizado no destino, consultar sem sessão ficha, relatório, anexo autorizado e filtros/retorno. Se não houver, a consulta positiva de conteúdo publicado mantém a prova QA aceite e o catálogo vazio é o resultado esperado no destino; não importar exemplos nem publicar um anúncio só para preencher o catálogo. Prorrogação/publicação/resultado já estão cobertos em QA e não são repetidos sobre anúncios reais durante a instalação.
|
||||
- Conferir que instalação/activação não criou qualquer versão com `published_at` preenchido, não mudou `published_version_id` de anúncios existentes e não alterou documentos/grants. Anúncios que a equipa publicar posteriormente são acções distintas, identificadas pela auditoria.
|
||||
|
||||
**Reversão com preservação.** Em falha de instalação antes do merge, manter flag desligada e release antiga; conservar tabelas eventualmente criadas até diagnosticar a falha e repor o contrato anterior se necessário. Em falha após disponibilização, desligar primeiro a flag na configuração efectiva e preservar um novo snapshot/dump dos dados e ficheiros actuais. Preferir correcção progressiva. Se for preciso reverter código, usar o rollback atómico existente do pipeline para a release anterior compatível, com recibo e configuração desligada; conservar as três tabelas, `migrations`, `audit_events`, subscrições e armazenamento partilhado. Esse rollback de código não restaura a base. Conservar o contrato compatível com a linha de código que voltará a ser entregue e documentar qualquer reposição do ficheiro de contrato; a verificação por hashes continua obrigatória.
|
||||
|
||||
Não executar `down`, `migrate:rollback` global nem restaurar por cima da base activa o backup anterior à instalação: isso eliminaria anúncios, actos, acompanhamentos, auditoria e ficheiros criados depois. O `down()` da candidata recusa a remoção quando há concursos, mas essa guarda não substitui o plano de preservação. Um restauro extraordinário exige primeiro recuperar numa instância separada e reconciliar os dados posteriores, conservando IDs, versões, relações, hashes e originais; só trocar o destino com um plano próprio revisto. Desligar o módulo torna-o temporariamente indisponível, não elimina nem retira selectivamente anúncios. Ao reactivar a release corrigida, recuperar e conferir os dados existentes.
|
||||
|
||||
Origem dos limites: QA/revisão antes de integração e ausência de publicação real na instalação são decisões expressas da Tempo; backup/restauro e autenticação de alterações no destino são requisitos permanentes existentes; pausa coordenada, schema antes de código, flag e contrato são escolhas técnicas impostas pela compatibilidade observada do pipeline. Não constituem nova cadeia organizacional de aprovação. Depois da instalação, as operações correntes de cadastro, prévia, publicação, prorrogação e actualização permanecem na gestão única, sem comandos ou Codex por concurso. As únicas intervenções técnicas deste plano são a instalação/activação e eventual recuperação da plataforma.
|
||||
|
||||
### Instalação concluída na homologação — 22/10/2026
|
||||
|
||||
Autorização expressa da Tempo nesta conversa: **«pode instalar»**, abrangendo a candidata `1bcf34c0` e o procedimento `e46f9731`. A instalação foi executada por SSH autenticado já disponível; a verificação da interface foi feita pela conta real Osmane Nala, ID 15, após renovação de login/MFA pelo utilizador. Não houve criação de contas, papéis ou aprovadores. A ausência de outras contas não bloqueou esta autorização existente. O guia continua sem declaração de conferência/aceitação pela Tempo.
|
||||
|
||||
Sequência efectivamente concluída: pausa coordenada do observador de deploy → backup → restauro isolado conferido → migration selectiva com flag desligada → conferência do schema e contrato → integração/push Git → build/promoção automática → activação separada. Integração funcional `f4d0970917e7e8d141f74c136b66138b929141fc`, confirmada em `/build/revision.json`. Activação às **22h51 de 22/10/2026 (África/Johannesburgo; 20h51 UTC)**. Os commits documentais posteriores conservam o mesmo código funcional. O pipeline está novamente activo e conserva o gate do contrato de migrations; não foi alterado para executar SQL de produção.
|
||||
|
||||
Backup privado no servidor: `/var/backups/investmoz-procurement-20261022/backup`. Dump SHA-256 `69de817b288ecb25db4b341375475f2408a47642a8ba69d338b217dcc982854f`; arquivo de ficheiros/configuração `7b75c22e730b5600d6e97e1c6dbb4cd93982bdd47e9dd2a63de2359974132674`. Restauro realizado num PostgreSQL isolado em container sem rede, porta interna 55441: **171 tabelas**, contagens e checksums iguais ao snapshot exportado. Arquivo conferido: **341 ficheiros**, incluindo configuração privada; preservados os **340 ficheiros de conteúdo** no destino. Não foi copiada uma base QA para a homologação.
|
||||
|
||||
Schema instalado: **três tabelas, 21 colunas, três PK, cinco FK e um UNIQUE**, correspondentes à migration revista; registo em `migrations` conferido. A conferência inicial do executor tinha uma contagem de colunas incorrecta (22); foi corrigida para as 21 efectivamente declaradas e conferidas antes de actualizar o contrato, sem repetir ou desfazer a migration. Não se alteraram grants. Comparação dos dados preexistentes antes da prova de rascunho: apenas `migrations`, `cache` e `sessions` diferiam do backup; utilizadores, atribuições, biblioteca, oportunidades, cadastro territorial e geometrias permaneciam iguais por checksum. Scanner ClamAV real disponível, assinaturas de 22/10/2026.
|
||||
|
||||
Conferência do delta no destino:
|
||||
|
||||
- `/concursos` responde 200 sem sessão ou autenticação HTTP adicional; catálogo vazio é esperado, porque não foram publicados anúncios. Gestão sem sessão redirecciona para login. Conta simples existente não tem autorização de escrita; recusas por perfil e operações editoriais conservam as provas QA aceites, sem criar uma conta de teste no destino.
|
||||
- Gestão única da conta real apresenta **Novo concurso/anúncio**. Rascunho `b970f0f0-9257-42e1-8365-3358237b55db`, referência `INSTALL-CHECK-20261022`, claramente identificado como **VERIFICAÇÃO DE INSTALAÇÃO — NÃO PUBLICAR**: guardado, recuperado pela lista e pré-visualizado na interface. Um acto de preparação auditado (`tender_draft_saved`, actor 15). Preservado como rascunho privado; não é um concurso real.
|
||||
- Ficha e impressão desse rascunho devolvem 404 sem sessão; download de documento privado devolve 404. Não se alterou a disponibilidade dos documentos existentes. **Uma preparação, uma versão de rascunho, zero versões publicadas e zero acompanhamentos.** Nenhum anúncio real foi publicado pela instalação ou como prova. Não foram transportados exemplos QA.
|
||||
- Publicação/prorrogação/resultados/cancelamento positivos conservam os 8 testes/285 asserções e a passagem visual QA aceites; não foram repetidos sobre anúncios reais na homologação. O build de entrega executou as suas verificações isoladas normais.
|
||||
|
||||
Recibo resumido versionado: [procurement-installation-receipt-20261022.json](procurement-installation-receipt-20261022.json). Recibos técnicos completos, logs e backups permanecem privados; capturas e recibo local em `outputs/procurement-install-20261022/`. A reversão continua a ser desligar a flag e, se necessário, reverter código compatível mantendo tabelas, versões, auditoria e armazenamento; não substituir dados novos pelo backup anterior.
|
||||
|
||||
**Links finais e guia de operação:** entrar na [gestão única](https://teste-investidor.tempo.co.mz/gestao-conteudos) → **Novo concurso/anúncio** → preencher regime/edição, entidade, objecto, calendário/fuso e canal → **Guardar rascunho**. Recuperar em **Gerir concursos e anúncios → Recuperar e editar**. Carregar/editar originais pela biblioteca ligada no formulário, acompanhar o scan e autorizar a divulgação dos anexos; voltar ao editor e actualizar as opções. **Preparar prévia pública** → rever conteúdo e motivo público → **Publicar este acto**, quando a equipa pretender publicar um anúncio autorizado. Para actualizar ou prorrogar, guardar a revisão e escolher o acto correspondente; a prorrogação mostra prazo anterior e novo. Resultado ou cancelamento seguem o mesmo percurso. O rascunho de instalação não se destina a publicação.
|
||||
|
||||
Consulta: [Concursos e anúncios](https://teste-investidor.tempo.co.mz/concursos). Abrir ficha para documentos e relatório imprimível; uma conta simples pode acompanhar, sem apresentar proposta. Documentos, oportunidades, divulgação promocional e concursos mantêm as distinções do guia anterior. **Não restam comandos ou intervenção do Codex para cada concurso** neste âmbito; a instalação e activação técnica estão concluídas. Geometrias e permissões foram preservadas.
|
||||
|
||||
### Legado — correspondência integral e avanço privado QA, 22/10/2026
|
||||
|
||||
Fonte do saldo: conferência documental fornecida pela Tempo e [InvesteMoz — entregas, Fintech e classificação](https://chatgpt.com/space/page_d6f090dba9048191a92c9175b3605dee), consultada nesta passagem; originais e reconciliação de 15/10 preservados. O CSV antigo ainda tinha 17 publicados/31 por integrar. Reconciliados os três artigos posteriores, sem os duplicar: **20 publicados, 28 por integrar na homologação, um vazio e um sem título = 50 identidades**. Os 29 recursos da biblioteca e as onze variantes de URL não são contagens de artigos distintos.
|
||||
|
||||
Lista única para a Tempo: [título original → endereço original → endereço no novo portal → estado → pendência](../v2/auditoria-portal/legado-correspondencia-integral.md). Actualizado também o [CSV existente dos 50 artigos](../v2/auditoria-portal/legado-conteudos-reconciliados.csv); mantém IDs, títulos, origens, hashes e referências anteriores, com campos explícitos para o estado consolidado e data da conferência. Não se criou nova matriz/backlog. Endereços novos ausentes ficam em branco; não foram fabricadas páginas para os conteúdos ainda não disponibilizados.
|
||||
|
||||
**Nova conferência pública, não nova aceitação funcional:** 20/20 páginas na homologação responderam sem login, com componente `portal/legacy`, ID legado, origem e corpo não vazio correspondentes. A identidade não foi deduzida apenas de HTTP 200 nem do título. IDs da biblioteca foram obtidos da resposta real do destino, sem transportar IDs QA. Consulta HTTP em 22/10/2026 às 23h05 (21h05 UTC). Não é revisão visual integral de cada artigo nem confirmação de actualidade de todas as afirmações. Imagens anteriores não foram reimportadas ou reanalisadas; as referências públicas das fichas foram registadas, sem repetir as provas aceites dos binários.
|
||||
|
||||
**Cartões educativos:** a branch `codex/educational-content-cards` já é ancestral de `main`, e o componente com imagem maior/resumo/«Ler o guia» está servido. Conferência visual no destino [Explorar Turismo](https://teste-investidor.tempo.co.mz/explorar/turismo): 12 cartões/links «Ler o guia». Corrige a indicação histórica de revisão ainda pendente, sem atribuir à Tempo uma conferência que não fez. Captura `outputs/legacy-correspondence-20261022/guides-homologation.png`.
|
||||
|
||||
**28 conteúdos avançados em QA, sem publicar:** dois sectoriais, oito de Trabalho Doméstico e 18 históricos/noticiosos. Preservado o corpo HTML de cada captura, hash do corpo, ID, autor, categoria, datas e origem. Derivadas JSON identificadas pelo formato existente, armazenadas privadamente; imagens não resolvidas não foram substituídas por um fundo genérico nem marcadas como disponíveis. O mecanismo existente `documents:import-batch` foi reutilizado exclusivamente em `simulator_fiscal_delta_20261021_test`, porta 55441, com guarda da identidade real. Não foi alargado o importador activo ou o seu âmbito nominal. Conta sintética QA já existente 239; sem novo grant persistente. **28 rascunhos internos, 28 scans reais limpos e 28 reutilizações por SHA-256 na repetição**, sem duplicações; publicações e atribuições preservadas.
|
||||
|
||||
A primeira passagem importou dez e detectou colisões de identidade nos restantes: o manifesto usava emissor/data/versão iguais para artigos distintos. Corrigida a versão documental dos itens restantes para incluir o ID original do artigo; os dez já criados foram reutilizados por bytes. Sem apagar/recriar originais, simular scan ou classificar artigos como diplomas oficiais. Esta era uma escolha de metadados da preparação, não uma decisão organizacional da Tempo nem uma razão para impedir o cadastro. O importador não foi alterado e os títulos originais foram preservados. Preparações, manifesto, logs e recibo completos mantidos em caminhos privados QA; o recibo versionado contém somente metadados de rastreabilidade.
|
||||
|
||||
**Lacunas originais conferidas na rede:** [Mercado Financeiro, ID 171994](https://investemoz.tempo.co.mz/mercado-financeiro), HTTP 200, corpo vazio, hash igual à captura. [Post 74364](https://investemoz.tempo.co.mz/74364-2), HTTP 200, título vazio, mesmo corpo capturado (274 caracteres HTML, 21 úteis no inventário). Não se encontrou conteúdo/título novo a recuperar. A resolução editorial destes dois exemplares depende do texto/título da Tempo; não se inventou um título nem se usou Fintech/imagem para preencher o artigo vazio. Não bloqueiam os outros 48.
|
||||
|
||||
**Saldo de integração conservado:** os 28 rascunhos QA ainda exigem fechar edição/leitor e entrega/publicação por lotes na homologação. Trabalho Doméstico e notícias mantêm âmbito/data históricos; não foram convertidos em orientação jurídica actual, regras executáveis ou oportunidades vigentes. As dependências de 15/10 estão identificadas como históricas, não como novos requisitos de validação. Nenhum conteúdo novo foi publicado no destino, não houve alteração de schema, mapas, permissões ou integração em `main` nesta passagem. A integração integral continua aberta; não confundir preparação privada QA com publicação dos 28.
|
||||
|
||||
Evidência consolidada: [recibo de correspondência, consulta e importação QA](../v2/auditoria-portal/legado-correspondencia-recibo-20261022.json). Ferramenta `tools/legacy-correspondence-audit.py`; `--reuse-http` regenera a lista sem repetir as consultas públicas. Os hashes das capturas/respostas são distintos dos hashes dos corpos/originais; não foram usados para alegar uma nova edição editorial.
|
||||
|
||||
### Legado — edição e leitura dos 28 rascunhos em QA, 22/10/2026
|
||||
|
||||
Actualiza o saldo anterior: **20 públicos já conferidos, 28 rascunhos privados com editor/leitor disponíveis em QA e dois originais incompletos**. Não houve nova importação, repetição da conferência dos 20, integração em `main`, transporte ou publicação na homologação nesta passagem. A Tempo não declarou ter conferido o guia local ou esta candidata.
|
||||
|
||||
**Guia curto da equipa.** Em [gestão única QA](http://127.0.0.1:18580/gestao-conteudos), abrir o acervo e localizar o título. **Editar artigo** recupera o corpo do original, a origem e as datas; corrigir o texto e escolher conservar a imagem de origem, seleccionar uma imagem pública verificada da biblioteca ou não mostrar imagem. **Pré-visualizar** mostra as alterações ainda sem guardar e não publica. **Guardar edição** cria uma revisão, preserva o original e retira eventual publicação até nova decisão expressa. **Ler revisão guardada** recupera a edição persistida. Metadados, classificação/direitos, histórico e publicação continuam no acervo ligado pelo editor; **Publicar** é uma acção distinta, sujeita às permissões e campos já existentes. Para actualizar um artigo publicado, guardar a revisão, rever a prévia e publicar novamente. A consulta pública usa `/artigos/{id-do-destino}` e o acesso pela biblioteca; rascunhos não ficam acessíveis por esse endereço.
|
||||
|
||||
O leitor mantém a estrutura HTML sanitizada do original enquanto o corpo não for alterado; uma correcção textual fica como edição separada, sem reescrever o ficheiro capturado. Datas e autor de origem continuam identificados. Conteúdos históricos não se tornam oportunidades actuais, diplomas oficiais ou regras de cálculo. A revisão QA do artigo 62413 contém uma nota de demonstração explicitamente marcada **QA**, guardada pela interface: deve ser revista/corrigida antes de publicação real; não foi removida silenciosamente do pacote.
|
||||
|
||||
**Imagens e lacunas.** Preservadas as 16 referências a imagens no corpo e a referência de capa dos 28 originais. Não se descarregam automaticamente imagens externas. Nesta QA não há exemplar público verificado da capa comum: a prévia informa a ausência e conserva a referência. A capa já aceite no destino deve ser reutilizada pelo SHA-256 `28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa`, sem confundir igualdade de URL com igualdade de bytes. Pode seleccionar-se outra imagem autorizada pela interface; nenhuma imagem foi marcada como disponível sem binário verificado. Os dois casos incompletos na origem ficam fora deste lote.
|
||||
|
||||
**Permissões e origem das restrições.** Reutilizados os gestores e as designações editoriais existentes, email verificado, MFA aplicável e exclusão de escrita para auditor/conta simples. Nenhum papel ou aprovador novo. A inclusão destes 28 originais no âmbito editorial decorre do pedido expresso da Tempo. A correspondência por hash exacto dos originais é uma escolha técnica: não depende de IDs ou etiquetas de importação QA que mudem no destino. Validação dos bytes, scan e classificação/direitos seguem requisitos anteriores; controlo de versão e prévia privada são escolhas técnicas para evitar sobreposição de edições e publicação acidental.
|
||||
|
||||
**Evidência delimitada.** Seis testes específicos, **1.000 asserções**, passaram na base isolada `simulator_fiscal_delta_20261021_test:55441`, com transacções revertidas. Percorrem os 28 editores/prévias, edição/recuperação/histórico, conflito de versão, publicação explícita e retirada, classificação, imagem verificada/retirada, sanitização e permissões nominais/MFA. A prova de publicação dos testes foi revertida; os 28 continuam privados. TypeScript e build QA passaram. Na interface, conta sintética QA existente guardou/recuperou a revisão 2 do artigo 62413; leituras de Alumínio “Verde” e do guia de Trabalho Doméstico demonstradas. Não houve alteração de contas reais. Capturas e logs em `outputs/legacy-editorial-20261022/`; [recibo resumido](../v2/auditoria-portal/legado-editorial-qa-20261022.json).
|
||||
|
||||
**Preparação concreta da entrega privada.** `outputs/legacy-editorial-20261022/legacy-editorial-private-v2.zip`, 85 614 bytes, **57 entradas**: 28 originais, 28 snapshots/históricos de proveniência e manifesto. SHA-256 `177c50d1cc3b1610e62123a2f8b532711a15f875aac2ee0e372636c2f747e8a2`. Conferidos os 28 hashes e a integridade do ZIP; zero imagens binárias incluídas e zero escritas de base pelo exportador. Manifesto com `publish=false`, edições actuais e vínculos de imagem por hash; sem credenciais ou cópia da base. `tools/prepare-legacy-editorial-delivery.php` só exporta a QA nominal, não instala/importa no destino. Originais e histórico completo ficam privados; IDs/actores QA são proveniência, não identidades ou autores de auditoria no destino.
|
||||
|
||||
**Sequência para homologação, ainda não executada:**
|
||||
|
||||
1. Rever e integrar a candidata pelo processo Git existente. Não há migration, alteração de schema ou nova dependência de pacote; o deploy de código não transporta estes conteúdos nem publica artigos.
|
||||
2. Antes de escrever no destino, conferir revisão servida, identidade da base e backup consistente de base/armazenamento com restauro isolado demonstrado para a janela. Transportar o ZIP para armazenamento privado, fora da árvore pública, e conferir o hash. Preservar o recibo e o manifesto.
|
||||
3. Com autenticação e permissões existentes, usar o cadastro/upload da biblioteca para cada original ainda ausente, com scan real e classificação privada. Resolver por hash e identidade/versão: reutilizar originais iguais, registar IDs reais e parar apenas o item com conflito, sem sobrescrever. Não repetir a importação QA nem copiar a sua base. Não usar os importadores nominais de Agronegócio/lotes antigos ou um `--actor` para contornar os limites activos; o exportador não alarga esses executores.
|
||||
4. Recuperar os recursos pelo portal; aplicar metadados/edição actual pelos formulários autenticados, usando a versão do destino e criando histórico atribuído ao actor real. Conservar os snapshots QA privadamente como proveniência, sem inserir actores QA na auditoria activa. Reutilizar a capa aceite por hash; referências sem binário mantêm aviso. Conferir **28 privados, zero publicações**, correspondência original → recurso real e hash, revisão guardada e acessos previstos. Actualizar a lista única com os endereços efectivamente demonstrados.
|
||||
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,51 +1,51 @@
|
||||
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
|
||||
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,5,publicados,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 6, ""sha256"": ""5e3840705cf98226d1f189cf665aaa98cb05da96c645159a1332a1d429d642eb""}",,Reutilizar a versão e a aceitação existentes; não importar ou aceitar novamente.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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,,existente por integrar,inventariado; cadastro/versão por reconciliar,,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}","Referência não recolhida: http://www.newsaiep.com/moz_news/wp-content/uploads/817802064_1_644x461_15-30-costa-do-sol-com-dua-na-zona-de-mupulene-muito-lindo-maputo.jpg | Referência não recolhida: https://www.olx.co.mz/imoveis-casas/terrenos/terrenos-venda/maputo-maputo/?search%5Bprivate_business%5D=private | Notícia/anúncio histórico; definir destino de consulta e prioridade pela Tempo, sem converter em orientação atual ou oportunidade vigente.",Preparar apenas se escolhido para o próximo lote; não publicar por esta reconciliação.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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,,existente por integrar,inventariado; cadastro/versão por reconciliar,,setorial,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Ausente da biblioteca ativa e QA; falta exemplar versionado no manifesto de um novo lote e extensão nominal do importador. | Slug ausente no mapa do leitor; falta ligação ao percurso/dossier fundamentada para este artigo. | Não há setor mestre Alumínio Verde com correspondência exata; fundamentar o agrupamento sem inventar vínculo a projeto ou criar setor automaticamente.,Preparar apenas se escolhido para o próximo lote; não publicar por esta reconciliação.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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,,existente por integrar,inventariado; cadastro/versão por reconciliar,,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}","Referência não recolhida: http://opais.sapo.mz/index.php/economia/38-economia/45893-banco-mundial-classifica-de-proibitivos-juros-praticados-em-mocambique.html | Referência não recolhida: http://www.newsaiep.com/moz_news/wp-content/uploads/Banco-Mundial-classifica-de-proibitivos-juros-praticados-em-Mo%C3%A7ambique.jpg | Referência não recolhida: https://www.facebook.com/196034093767187/posts/1460805843956666 | Notícia/anúncio histórico; definir destino de consulta e prioridade pela Tempo, sem converter em orientação atual ou oportunidade vigente.",Preparar apenas se escolhido para o próximo lote; não publicar por esta reconciliação.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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 no ativo — setores-fintech-QA-d5b4dc354265 DOC01; importação privada, scan, metadados e publicação separados",inventariado; cadastro/versão por reconciliar,29,publicado,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Entrega concluída; nenhum novo envio ou republicação da imagem. Regulação específica Fintech permanece na matriz.,"Preservar publicação; restante acervo separado, sem repetir aceitação.",outputs/investmoz-fintech-investimento-QA-20261015/RESULTADO-QA.md; PROVA-DELTA-QA.json; HTTP-DELTA-QA.json; PROVA-FINAL-DELTA-QA.json; HTTP-RECUPERACAO-FINAL-QA.json; MANIFESTO-ENTREGA.json | outputs/investmoz-setores-fintech-ativacao-20261015/RELATORIO-ENTREGA.md; PUBLISHED.json; PRESERVACAO-FINAL.json; HTTP-OPERACIONAL.json; FRESH-BACKUP.json; RECIBO-FINAL.json
|
||||
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.,20,publicados,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",,Reutilizar a versão e a aceitação existentes; não importar ou aceitar novamente.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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,,existente por integrar,inventariado; cadastro/versão por reconciliar,,trabalho_domestico,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}","Conteúdo editorial não oficial; falta recorte e ligação ao consumidor/apoio apropriado. Eventual uso jurídico ou cálculo requer citação/âmbito, sem revalidar credibilidade de fontes oficiais já aceites.",Preparar apenas se escolhido para o próximo lote; não publicar por esta reconciliação.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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,,existente por integrar,inventariado; cadastro/versão por reconciliar,,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}","Referência não recolhida: https://www.facebook.com/photo.php?fbid=741844132690980&set=gm.1566816196708735&type=3 | Referência não recolhida: http://www.newsaiep.com/moz_news/wp-content/uploads/Coloque-o-seu-n%C3%BAmero-para-o-grupo-de-whatsapp-venda-de-acess%C3%B3rios-de-computador.jpg | Referência não recolhida: http://www.newsaiep.com/moz_news/wp-content/uploads/1502293415_298_Coloque-o-seu-n%C3%BAmero-para-o-grupo-de-whatsapp-venda-de-acess%C3%B3rios-de-computador.jpg | Referência não recolhida: https://www.facebook.com/179463555444013/posts/1566816196708735 | Notícia/anúncio histórico; definir destino de consulta e prioridade pela Tempo, sem converter em orientação atual ou oportunidade vigente.",Preparar apenas se escolhido para o próximo lote; não publicar por esta reconciliação.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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,,existente por integrar,inventariado; cadastro/versão por reconciliar,,trabalho_domestico,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}","Conteúdo editorial não oficial; falta recorte e ligação ao consumidor/apoio apropriado. Eventual uso jurídico ou cálculo requer citação/âmbito, sem revalidar credibilidade de fontes oficiais já aceites.",Preparar apenas se escolhido para o próximo lote; não publicar por esta reconciliação.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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.,7,publicados,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 23, ""sha256"": ""0a2c50d91dad3a6007544634540e0f61b11d3506682a5a19066bae03cdcba490""}",,Reutilizar a versão e a aceitação existentes; não importar ou aceitar novamente.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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,,existente por integrar,inventariado; cadastro/versão por reconciliar,,setorial,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}","Ausente da biblioteca ativa e QA; falta exemplar versionado no manifesto de um novo lote e extensão nominal do importador. | Slug ausente no mapa do leitor; falta ligação ao percurso/dossier fundamentada para este artigo. | Texto distinto de Energia/Mineração já publicado; preservar a ligação declarada /energia-mineracao e delimitar agrupamento, sem fundir os exemplares.",Preparar apenas se escolhido para o próximo lote; não publicar por esta reconciliação.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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,,existente por integrar,inventariado; cadastro/versão por reconciliar,,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}","Referência indisponível na captura (HTTP 404): https://investemoz.tempo.co.mz/images/ANO-2017/JUNHO/21/GOVERNADOR-in.gif | Notícia/anúncio histórico; definir destino de consulta e prioridade pela Tempo, sem converter em orientação atual ou oportunidade vigente.",Preparar apenas se escolhido para o próximo lote; não publicar por esta reconciliação.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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,,existente por integrar,inventariado; cadastro/versão por reconciliar,,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}","Referência não recolhida: http://www.newsaiep.com/moz_news/wp-content/uploads/a84c6c1e4d1780f63086a86817521b40 | Notícia/anúncio histórico; definir destino de consulta e prioridade pela Tempo, sem converter em orientação atual ou oportunidade vigente.",Preparar apenas se escolhido para o próximo lote; não publicar por esta reconciliação.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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,,existente por integrar,inventariado; cadastro/versão por reconciliar,,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}","Referência não recolhida: http://opais.sapo.mz/index.php/sociedade/45-sociedade/45963-governo-pretende-construir-100-mil-habitacoes-em-dois-anos.html | Referência não recolhida: http://www.newsaiep.com/moz_news/wp-content/uploads/Governo-pretende-construir-100-mil-habita%C3%A7%C3%B5es-em-dois-anos.jpg | Referência não recolhida: https://www.facebook.com/196034093767187/posts/1464602060243711 | Notícia/anúncio histórico; definir destino de consulta e prioridade pela Tempo, sem converter em orientação atual ou oportunidade vigente.",Preparar apenas se escolhido para o próximo lote; não publicar por esta reconciliação.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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,,existente por integrar,inventariado; cadastro/versão por reconciliar,,trabalho_domestico,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}","Conteúdo editorial não oficial; falta recorte e ligação ao consumidor/apoio apropriado. Eventual uso jurídico ou cálculo requer citação/âmbito, sem revalidar credibilidade de fontes oficiais já aceites.",Preparar apenas se escolhido para o próximo lote; não publicar por esta reconciliação.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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 no ativo — setores-fintech-QA-d5b4dc354265 DOC01; importação privada, scan, metadados e publicação separados",inventariado; cadastro/versão por reconciliar,28,publicado,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Entrega concluída; nenhum novo envio ou republicação da imagem. Regulação específica Fintech permanece na matriz.,"Preservar publicação; restante acervo separado, sem repetir aceitação.",outputs/investmoz-fintech-investimento-QA-20261015/RESULTADO-QA.md; PROVA-DELTA-QA.json; HTTP-DELTA-QA.json; PROVA-FINAL-DELTA-QA.json; HTTP-RECUPERACAO-FINAL-QA.json; MANIFESTO-ENTREGA.json | outputs/investmoz-setores-fintech-ativacao-20261015/RELATORIO-ENTREGA.md; PUBLISHED.json; PRESERVACAO-FINAL.json; HTTP-OPERACIONAL.json; FRESH-BACKUP.json; RECIBO-FINAL.json
|
||||
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.,22,publicados,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 26, ""sha256"": ""8e58544e49e719f9c865c91e07ef1030155a9e3d59260e6dc93e9c9e25674e39""}",,Reutilizar a versão e a aceitação existentes; não importar ou aceitar novamente.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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.,16,publicados,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",,Reutilizar a versão e a aceitação existentes; não importar ou aceitar novamente.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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,,existente por integrar,inventariado; cadastro/versão por reconciliar,,trabalho_domestico,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}","Conteúdo editorial não oficial; falta recorte e ligação ao consumidor/apoio apropriado. Eventual uso jurídico ou cálculo requer citação/âmbito, sem revalidar credibilidade de fontes oficiais já aceites.",Preparar apenas se escolhido para o próximo lote; não publicar por esta reconciliação.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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.,21,publicados,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",,Reutilizar a versão e a aceitação existentes; não importar ou aceitar novamente.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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,,existente por integrar,inventariado; cadastro/versão por reconciliar,,trabalho_domestico,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}","Conteúdo editorial não oficial; falta recorte e ligação ao consumidor/apoio apropriado. Eventual uso jurídico ou cálculo requer citação/âmbito, sem revalidar credibilidade de fontes oficiais já aceites.",Preparar apenas se escolhido para o próximo lote; não publicar por esta reconciliação.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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.,11,publicados,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",,Reutilizar a versão e a aceitação existentes; não importar ou aceitar novamente.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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.,18,publicados,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",,Reutilizar a versão e a aceitação existentes; não importar ou aceitar novamente.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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.,15,publicados,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",,Reutilizar a versão e a aceitação existentes; não importar ou aceitar novamente.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
172258,"Província de Manica: Onde o Turismo de Montanha, Natureza e Cultura Encontra o Próximo Ciclo de Investimento",tempo,Turismo,2026-04-14T14:18:28+00:00,2026-04-17T10:42:16+00:00,4c8bdd79212ba98fa347733e2f58aef8796b29cc8cb8b649d200bd37435df1c3,https://investemoz.tempo.co.mz/provincia-de-manica-onde-o-turismo-de-montanha-natureza-e-cultura-encontra-o-proximo-ciclo-de-investimento/ | https://investemoz.tempo.co.mz/provincia-de-manica-onde-o-turismo-de-montanha-natureza-e-cultura-encontra-o-proximo-ciclo-de-investimento,14,publicado,RecursoQA14; original/hash/ClamD/replay demonstrados. DivulgaçãoQAseparada;consumidorHTTP/desktop observado;não é oportunidade nem geometria.,14,publicados,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",,Reutilizar a versão e a aceitação existentes; não importar ou aceitar novamente.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
172237,"Província de Maputo: Natureza, Cultura e Conectividade como Motores de Crescimento Sustentável",tempo,Turismo,2026-04-14T10:55:21+00:00,2026-04-17T08:27:24+00:00,d112d0e535912f4487c2b8d41978e5e663f1db43f1a2cdf170dbbec535f13f27,https://investemoz.tempo.co.mz/provincia-de-maputo-turismo/ | https://investemoz.tempo.co.mz/provincia-de-maputo-turismo,17,publicado,RecursoQA17; original/hash/ClamD/replay demonstrados. DivulgaçãoQAseparada;consumidorHTTP/desktop observado;não é oportunidade nem geometria.,17,publicados,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",,Reutilizar a versão e a aceitação existentes; não importar ou aceitar novamente.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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.,19,publicados,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",,Reutilizar a versão e a aceitação existentes; não importar ou aceitar novamente.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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.,12,publicados,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",,Reutilizar a versão e a aceitação existentes; não importar ou aceitar novamente.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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.,13,publicados,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",,Reutilizar a versão e a aceitação existentes; não importar ou aceitar novamente.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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,,existente por integrar,inventariado; cadastro/versão por reconciliar,,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}","Referência não recolhida: http://opais.sapo.mz/index.php/sociedade/45-sociedade/45870-rofino-licuco-preso-cinco-meses-depois-de-condenado-por-agressao-.html | Referência não recolhida: http://www.newsaiep.com/moz_news/wp-content/uploads/Rofino-Licuco-preso-cinco-meses-depois-de-condenado-por-agress%C3%A3o.jpg | Referência não recolhida: https://www.facebook.com/196034093767187/posts/1458270617543522 | Notícia/anúncio histórico; definir destino de consulta e prioridade pela Tempo, sem converter em orientação atual ou oportunidade vigente.",Preparar apenas se escolhido para o próximo lote; não publicar por esta reconciliação.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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,,existente por integrar,inventariado; cadastro/versão por reconciliar,,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}","Referência não recolhida: http://opais.sapo.mz/index.php/sociedade/45-sociedade/46074-sete-individuos-mortos-a-tiro-na-matola-.html | Referência não recolhida: http://www.newsaiep.com/moz_news/wp-content/uploads/Sete-indiv%C3%ADduos-mortos-a-tiro-na-Matola.jpg | Referência não recolhida: https://www.facebook.com/196034093767187/posts/1471269102910340 | Notícia/anúncio histórico; definir destino de consulta e prioridade pela Tempo, sem converter em orientação atual ou oportunidade vigente.",Preparar apenas se escolhido para o próximo lote; não publicar por esta reconciliação.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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,,existente por integrar,inventariado; cadastro/versão por reconciliar,,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}","Referência não recolhida: http://www.verdade.co.mz/internacional/62604-sete-pessoas-morrem-e-150-sao-resgatados-em-acidente-com-electrico-na-india | Notícia/anúncio histórico; definir destino de consulta e prioridade pela Tempo, sem converter em orientação atual ou oportunidade vigente.",Preparar apenas se escolhido para o próximo lote; não publicar por esta reconciliação.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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 no ativo — setores-fintech-QA-d5b4dc354265 DOC01; importação privada, scan, metadados e publicação separados",inventariado; cadastro/versão por reconciliar,27,publicado,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",Entrega concluída; nenhum novo envio ou republicação da imagem. Regulação específica Fintech permanece na matriz.,"Preservar publicação; restante acervo separado, sem repetir aceitação.",outputs/investmoz-fintech-investimento-QA-20261015/RESULTADO-QA.md; PROVA-DELTA-QA.json; HTTP-DELTA-QA.json; PROVA-FINAL-DELTA-QA.json; HTTP-RECUPERACAO-FINAL-QA.json; MANIFESTO-ENTREGA.json | outputs/investmoz-setores-fintech-ativacao-20261015/RELATORIO-ENTREGA.md; PUBLISHED.json; PRESERVACAO-FINAL.json; HTTP-OPERACIONAL.json; FRESH-BACKUP.json; RECIBO-FINAL.json
|
||||
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.,10,publicados,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 25, ""sha256"": ""fe9a2e382e385b35f6e61ae320e199a979678677fd327fb7d4b07d8ce0111c11""}",,Reutilizar a versão e a aceitação existentes; não importar ou aceitar novamente.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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,,existente por integrar,inventariado; cadastro/versão por reconciliar,,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}","Referência não recolhida: http://opais.sapo.mz/index.php/desporto/74-desporto/45358-daude-razaque-ja-nao-e-treinador-da-liga-desportiva-de-maputo.html | Referência não recolhida: http://www.newsaiep.com/moz_news/wp-content/uploads/Da%C3%BAde-Razaque-j%C3%A1-n%C3%A3o-%C3%A9-treinador-da-Liga-Desportiva-de-Maputo.jpg | Referência não recolhida: https://www.facebook.com/196034093767187/posts/1425759480794636 | Notícia/anúncio histórico; definir destino de consulta e prioridade pela Tempo, sem converter em orientação atual ou oportunidade vigente.",Preparar apenas se escolhido para o próximo lote; não publicar por esta reconciliação.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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,,existente por integrar,inventariado; cadastro/versão por reconciliar,,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}","Referência não recolhida: http://opais.sapo.mz/index.php/politica/63-politica/45340-daviz-simango-nao-se-incomoda-com-possivel-sucessao-na-beira-.html | Referência não recolhida: http://www.newsaiep.com/moz_news/wp-content/uploads/Daviz-Simango-n%C3%A3o-se-incomoda-com-poss%C3%ADvel-sucess%C3%A3o-na-Beira.jpg | Referência não recolhida: https://www.facebook.com/196034093767187/posts/1424683617568889 | Notícia/anúncio histórico; definir destino de consulta e prioridade pela Tempo, sem converter em orientação atual ou oportunidade vigente.",Preparar apenas se escolhido para o próximo lote; não publicar por esta reconciliação.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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,,existente por integrar,inventariado; cadastro/versão por reconciliar,,trabalho_domestico,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}","Conteúdo editorial não oficial; falta recorte e ligação ao consumidor/apoio apropriado. Eventual uso jurídico ou cálculo requer citação/âmbito, sem revalidar credibilidade de fontes oficiais já aceites.",Preparar apenas se escolhido para o próximo lote; não publicar por esta reconciliação.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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.,8,publicados,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",,Reutilizar a versão e a aceitação existentes; não importar ou aceitar novamente.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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,,existente por integrar,inventariado; cadastro/versão por reconciliar,,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}","Referência não recolhida: http://opais.sapo.mz/index.php/politica/63-politica/45345-exigimos-responsabilizacao-dos-agentes-do-crime-daviz-simango.html | Referência não recolhida: http://www.newsaiep.com/moz_news/wp-content/uploads/%E2%80%9CExigimos-responsabiliza%C3%A7%C3%A3o-dos-agentes-do-crime%E2%80%9D-Daviz-Simango.jpg | Referência não recolhida: https://www.facebook.com/196034093767187/posts/1424873994216518 | Notícia/anúncio histórico; definir destino de consulta e prioridade pela Tempo, sem converter em orientação atual ou oportunidade vigente.",Preparar apenas se escolhido para o próximo lote; não publicar por esta reconciliação.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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,,existente por integrar,inventariado; cadastro/versão por reconciliar,,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}","Referência não recolhida: http://opais.sapo.mz/index.php/sociedade/45-sociedade/45352-governo-e-parceiros-reforcam-mecanismos-de-controlo-de-hivsida.html | Referência não recolhida: http://www.newsaiep.com/moz_news/wp-content/uploads/Governo-e-parceiros-refor%C3%A7am-mecanismos-de-controlo-de-HIVSida.jpg | Referência não recolhida: https://www.facebook.com/196034093767187/posts/1425451974158720 | Notícia/anúncio histórico; definir destino de consulta e prioridade pela Tempo, sem converter em orientação atual ou oportunidade vigente.",Preparar apenas se escolhido para o próximo lote; não publicar por esta reconciliação.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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,,existente por integrar,inventariado; cadastro/versão por reconciliar,,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}","Referência não recolhida: http://opais.sapo.mz/index.php/sociedade/45-sociedade/45368-graca-machel-diz-que-desnutricao-e-um-problema-de-desenvolvimento.html | Referência não recolhida: http://www.newsaiep.com/moz_news/wp-content/uploads/Gra%C3%A7a-Machel-diz-que-desnutri%C3%A7%C3%A3o-%C3%A9-um-problema-de-desenvolvimento.jpg | Referência não recolhida: https://www.facebook.com/196034093767187/posts/1426040144099903 | Notícia/anúncio histórico; definir destino de consulta e prioridade pela Tempo, sem converter em orientação atual ou oportunidade vigente.",Preparar apenas se escolhido para o próximo lote; não publicar por esta reconciliação.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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,,existente por integrar,inventariado; cadastro/versão por reconciliar,,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}","Referência não recolhida: http://opais.sapo.mz/index.php/opiniao/182-helder-faife/45364-in-dependencia.html | Referência não recolhida: http://www.newsaiep.com/moz_news/wp-content/uploads/In-depend%C3%AAncia.jpg | Referência não recolhida: https://www.facebook.com/196034093767187/posts/1426022220768362 | Notícia/anúncio histórico; definir destino de consulta e prioridade pela Tempo, sem converter em orientação atual ou oportunidade vigente.",Preparar apenas se escolhido para o próximo lote; não publicar por esta reconciliação.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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,,existente por integrar,inventariado; cadastro/versão por reconciliar,,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}","Referência não recolhida: http://www.verdade.co.mz/tema-de-fundo/35/62615 | Referência não recolhida: http://www.newsaiep.com/moz_news/wp-content/uploads/Kroll-n%C3%A3o-descobriu-como-foram-gastos-2-bili%C3%B5es-de-d%C3%B3lares-Governo-de-Nyusi-n%C3%A3o-apoiou-a-Auditoria-cria%C3%A7%C3%A3o-da-Proindicus-EMATUM-e-MAM-n%C3%A3o-foi-para-prote%C3%A7%C3%A3o-da-Zona-Econ%C3%B3mica-Exclusiva.jpg | Referência não recolhida: https://www.facebook.com/113487338672967/posts/1492451004109920 | Notícia/anúncio histórico; definir destino de consulta e prioridade pela Tempo, sem converter em orientação atual ou oportunidade vigente.",Preparar apenas se escolhido para o próximo lote; não publicar por esta reconciliação.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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.,9,publicados,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}",,Reutilizar a versão e a aceitação existentes; não importar ou aceitar novamente.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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,,existente por integrar,inventariado; cadastro/versão por reconciliar,,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}","Referência não recolhida: http://www.verdade.co.mz/newsflash/62607 | Referência não recolhida: https://www.facebook.com/113487338672967/posts/1491493337539020 | Notícia/anúncio histórico; definir destino de consulta e prioridade pela Tempo, sem converter em orientação atual ou oportunidade vigente.",Preparar apenas se escolhido para o próximo lote; não publicar por esta reconciliação.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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,,existente por integrar,inventariado; cadastro/versão por reconciliar,,trabalho_domestico,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}","Conteúdo editorial não oficial; falta recorte e ligação ao consumidor/apoio apropriado. Eventual uso jurídico ou cálculo requer citação/âmbito, sem revalidar credibilidade de fontes oficiais já aceites.",Preparar apenas se escolhido para o próximo lote; não publicar por esta reconciliação.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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,,existente por integrar,inventariado; cadastro/versão por reconciliar,,historico_noticioso,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}","Referência não recolhida: http://www.verdade.co.mz/newsflash/62625 | Referência não recolhida: https://www.facebook.com/113487338672967/posts/1493133914041629 | Notícia/anúncio histórico; definir destino de consulta e prioridade pela Tempo, sem converter em orientação atual ou oportunidade vigente.",Preparar apenas se escolhido para o próximo lote; não publicar por esta reconciliação.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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,,existente por integrar,inventariado; cadastro/versão por reconciliar,,trabalho_domestico,"{""status"": ""imagem já publicada; reutilizar"", ""resource_id"": 24, ""sha256"": ""28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa""}","Conteúdo editorial não oficial; falta recorte e ligação ao consumidor/apoio apropriado. Eventual uso jurídico ou cálculo requer citação/âmbito, sem revalidar credibilidade de fontes oficiais já aceites.",Preparar apenas se escolhido para o próximo lote; não publicar por esta reconciliação.,outputs/investmoz-legado-inventario-reconciliado-20261015/RECONCILIACAO-LEGADO.json; LEITURA-ESTADO.json (SQL READ ONLY)
|
||||
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,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,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,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,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,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,
|
||||
172258,"Província de Manica: Onde o Turismo de Montanha, Natureza e Cultura Encontra o Próximo Ciclo de Investimento",tempo,Turismo,2026-04-14T14:18:28+00:00,2026-04-17T10:42:16+00:00,4c8bdd79212ba98fa347733e2f58aef8796b29cc8cb8b649d200bd37435df1c3,https://investemoz.tempo.co.mz/provincia-de-manica-onde-o-turismo-de-montanha-natureza-e-cultura-encontra-o-proximo-ciclo-de-investimento/ | https://investemoz.tempo.co.mz/provincia-de-manica-onde-o-turismo-de-montanha-natureza-e-cultura-encontra-o-proximo-ciclo-de-investimento,14,publicado,RecursoQA14; original/hash/ClamD/replay demonstrados. DivulgaçãoQAseparada;consumidorHTTP/desktop observado;não é oportunidade nem geometria.,20,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-manica-onde-o-turismo-de-montanha-natureza-e-cultura-encontra-o-proximo-ciclo-de-investimento,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,
|
||||
172237,"Província de Maputo: Natureza, Cultura e Conectividade como Motores de Crescimento Sustentável",tempo,Turismo,2026-04-14T10:55:21+00:00,2026-04-17T08:27:24+00:00,d112d0e535912f4487c2b8d41978e5e663f1db43f1a2cdf170dbbec535f13f27,https://investemoz.tempo.co.mz/provincia-de-maputo-turismo/ | https://investemoz.tempo.co.mz/provincia-de-maputo-turismo,17,publicado,RecursoQA17; original/hash/ClamD/replay demonstrados. DivulgaçãoQAseparada;consumidorHTTP/desktop observado;não é oportunidade nem geometria.,22,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-maputo-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,
|
||||
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,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,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,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,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
|
||||
|
||||
|
@@ -0,0 +1,58 @@
|
||||
# Correspondência integral dos 50 artigos do legado
|
||||
|
||||
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 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 | 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 | 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 | 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 | 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 | 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. |
|
||||
| 172258 | Província de Manica: Onde o Turismo de Montanha, Natureza e Cultura Encontra o Próximo Ciclo de Investimento | https://investemoz.tempo.co.mz/provincia-de-manica-onde-o-turismo-de-montanha-natureza-e-cultura-encontra-o-proximo-ciclo-de-investimento/ \| https://investemoz.tempo.co.mz/provincia-de-manica-onde-o-turismo-de-montanha-natureza-e-cultura-encontra-o-proximo-ciclo-de-investimento | https://teste-investidor.tempo.co.mz/provincia-de-manica-onde-o-turismo-de-montanha-natureza-e-cultura-encontra-o-proximo-ciclo-de-investimento | Publicado; identidade e conteúdo consultáveis na homologação | Não foi feita aceitação visual ou conferência pela Tempo nesta passagem. |
|
||||
| 172237 | Província de Maputo: Natureza, Cultura e Conectividade como Motores de Crescimento Sustentável | https://investemoz.tempo.co.mz/provincia-de-maputo-turismo/ \| https://investemoz.tempo.co.mz/provincia-de-maputo-turismo | https://teste-investidor.tempo.co.mz/provincia-de-maputo-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. |
|
||||
| 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 | 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 | 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 | 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 | 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. |
|
||||
@@ -0,0 +1,545 @@
|
||||
{
|
||||
"checked_utc": "2026-10-22T21:05:03.369388+00:00",
|
||||
"distinct_articles": 50,
|
||||
"publications_documented": 20,
|
||||
"public_pages_identity_verified": 20,
|
||||
"pending_content": 28,
|
||||
"private_preparation": {
|
||||
"historico_noticioso": 18,
|
||||
"setorial": 2,
|
||||
"trabalho_domestico": 8
|
||||
},
|
||||
"empty": 1,
|
||||
"untitled": 1,
|
||||
"database_writes_by_correspondence_script": 0,
|
||||
"active_database_writes": 0,
|
||||
"new_publications": 0,
|
||||
"qa_imports": 28,
|
||||
"qa_clean_scans": 28,
|
||||
"remaining_integration_complete": false,
|
||||
"public_pages": {
|
||||
"checked_utc": "2026-10-22T21:05:03.369388+00:00",
|
||||
"method": "Anonymous HTTP GET; Inertia article identity/source/content checked; no visual acceptance or new publication",
|
||||
"pages": {
|
||||
"173209": {
|
||||
"url": "https://teste-investidor.tempo.co.mz/tech-fintech",
|
||||
"status": 200,
|
||||
"identity_verified": true,
|
||||
"resource_id": 28,
|
||||
"document_sha256": "926f5e4096737c936c9d5002ebdc64be9e63b0c0822023d17ce618111a65c941",
|
||||
"current_title": "Fintech",
|
||||
"image_url": "/documentos/27/view",
|
||||
"response_sha256": "565f86bb5febd5833d5392588551b8e587b2d65898eda06284dcb33228866b63"
|
||||
},
|
||||
"173218": {
|
||||
"url": "https://teste-investidor.tempo.co.mz/imobiliario",
|
||||
"status": 200,
|
||||
"identity_verified": true,
|
||||
"resource_id": 7,
|
||||
"document_sha256": "43612c358c7573475cea24ef4825c5b7c7925306fa5861051220eed3f4e333b5",
|
||||
"current_title": "Imobiliário",
|
||||
"image_url": "/documentos/27/view",
|
||||
"response_sha256": "684840410238c5a199c581373d5cab233cdf869ccbf7d0da306a3d26181060b4"
|
||||
},
|
||||
"173213": {
|
||||
"url": "https://teste-investidor.tempo.co.mz/bens-de-consumo-franquias",
|
||||
"status": 200,
|
||||
"identity_verified": true,
|
||||
"resource_id": 29,
|
||||
"document_sha256": "0ba605ae43dec7f685610f828df884bc82cb4da67131fc9783b5cdbe4cc6d9f0",
|
||||
"current_title": "Bens de Consumo e Franquias",
|
||||
"image_url": "/documentos/27/view",
|
||||
"response_sha256": "65145d3b10d494d7e2a8d09736078f9272733c3ae48b5bce501ccffefd63f9bf"
|
||||
},
|
||||
"171985": {
|
||||
"url": "https://teste-investidor.tempo.co.mz/agronegocio",
|
||||
"status": 200,
|
||||
"identity_verified": true,
|
||||
"resource_id": 13,
|
||||
"document_sha256": "0929184a6d4645b9046512675394ad4f60575273f6dab0955f71cc7474a6a4e2",
|
||||
"current_title": "Agronegocio",
|
||||
"image_url": "/documentos/12/view",
|
||||
"response_sha256": "13c4aeb4307fa5efefa04a9c2cafe0a36b4ab243d93739aa2c1bfe2b0c4ff252"
|
||||
},
|
||||
"171981": {
|
||||
"url": "https://teste-investidor.tempo.co.mz/energia-mineracao",
|
||||
"status": 200,
|
||||
"identity_verified": true,
|
||||
"resource_id": 18,
|
||||
"document_sha256": "26a70f16cd511c579f08633655ff312db05122f81e44852dd4b163fa6eef85f1",
|
||||
"current_title": "Energia & Mineração",
|
||||
"image_url": "/documentos/14/view",
|
||||
"response_sha256": "10a0e0c00a291929f8923962f9cb31e90bbbb7d2e7eddb856a2ae43e30ca6d1b"
|
||||
},
|
||||
"172679": {
|
||||
"url": "https://teste-investidor.tempo.co.mz/energia",
|
||||
"status": 200,
|
||||
"identity_verified": true,
|
||||
"resource_id": 1,
|
||||
"document_sha256": "2003755b7dac53b43f4608e707321db6d2f29878926a0e74f4cf27627b70a9cb",
|
||||
"current_title": "Energia",
|
||||
"image_url": "/documentos/27/view",
|
||||
"response_sha256": "41e8ba52174d616cfd81eaa24e4d9135dcfd69a2e6c48e20bfce7b8de5b40166"
|
||||
},
|
||||
"172674": {
|
||||
"url": "https://teste-investidor.tempo.co.mz/mineracao",
|
||||
"status": 200,
|
||||
"identity_verified": true,
|
||||
"resource_id": 2,
|
||||
"document_sha256": "3ae780f082a8fa7c767635cce98563f5bc027c9656c9f41f8788843a17fa1f54",
|
||||
"current_title": "Mineração",
|
||||
"image_url": "/documentos/27/view",
|
||||
"response_sha256": "b078b87e56f3d68434573aa4d2a2c780845304f44cc65f5f2237732ab32eca7e"
|
||||
},
|
||||
"171990": {
|
||||
"url": "https://teste-investidor.tempo.co.mz/turismo",
|
||||
"status": 200,
|
||||
"identity_verified": true,
|
||||
"resource_id": 19,
|
||||
"document_sha256": "8cab862be245382dd4c1fd5fcb423c47aed7be3a22e0a7f8d5521510cc36bc72",
|
||||
"current_title": "Turismo",
|
||||
"image_url": "/documentos/15/view",
|
||||
"response_sha256": "471c561e89bf55642ec6aa43b9a27e1dd1f97d0e11e93c8ab95f508b3824da5b"
|
||||
},
|
||||
"172263": {
|
||||
"url": "https://teste-investidor.tempo.co.mz/provincia-da-zambezia-onde-os-corredores-de-mocambique-convergem-com-a-natureza-a-cultura-e-o-turismo-sustentavel",
|
||||
"status": 200,
|
||||
"identity_verified": true,
|
||||
"resource_id": 3,
|
||||
"document_sha256": "538495cd5b31047ac1c5993fdaf298ec1303330368248b251c87d201d31ff211",
|
||||
"current_title": "Província da Zambézia: Onde os Corredores de Moçambique Convergem com a Natureza, a Cultura e o Turismo Sustentável",
|
||||
"image_url": "/documentos/27/view",
|
||||
"response_sha256": "8629d5647b8ab3f4752ce7452cc131e5b64c0abc06afd25255231a562e0f534e"
|
||||
},
|
||||
"172256": {
|
||||
"url": "https://teste-investidor.tempo.co.mz/provincia-de-sofala-onde-a-natureza-a-historia-e-o-futuro-do-turismo-convergem",
|
||||
"status": 200,
|
||||
"identity_verified": true,
|
||||
"resource_id": 4,
|
||||
"document_sha256": "dcc29cbd36cf57e0a5c88dd312c84ee03c32a3d18ae9216d3715dc7b5a6960bf",
|
||||
"current_title": "Província de Sofala: Onde a Natureza, a História e o Futuro do Turismo Convergem",
|
||||
"image_url": "/documentos/27/view",
|
||||
"response_sha256": "16f08eaa50b150d73d85ca0b8dccc17ff2b61179813dd6ad0d78ec4309c599d4"
|
||||
},
|
||||
"172261": {
|
||||
"url": "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",
|
||||
"status": 200,
|
||||
"identity_verified": true,
|
||||
"resource_id": 5,
|
||||
"document_sha256": "88177fccd78d8516dba670c6f71272af062cc981d52d97adefe5bf77c613b16a",
|
||||
"current_title": "Província de Tete: Onde o Rio Zambeze, a Cultura Viva e a Conservação Comunitária Criam o Futuro do Turismo",
|
||||
"image_url": "/documentos/27/view",
|
||||
"response_sha256": "7c24f3bfddf8c5bee5ef1312e6d55f46e3ca48c7e48e3f363eac4c95979e6aca"
|
||||
},
|
||||
"172258": {
|
||||
"url": "https://teste-investidor.tempo.co.mz/provincia-de-manica-onde-o-turismo-de-montanha-natureza-e-cultura-encontra-o-proximo-ciclo-de-investimento",
|
||||
"status": 200,
|
||||
"identity_verified": true,
|
||||
"resource_id": 20,
|
||||
"document_sha256": "48314a576dea2f2dadf47d8f1eadd7ab58030606b02eebe17aa9bd6badffb7e9",
|
||||
"current_title": "Província de Manica: Onde o Turismo de Montanha, Natureza e Cultura Encontra o Próximo Ciclo de Investimento",
|
||||
"image_url": "/documentos/27/view",
|
||||
"response_sha256": "bac797abb94d97ffdc11f8b8cfe9b7782f2c8a9d2311c96e83f71f4ba86bffcb"
|
||||
},
|
||||
"172253": {
|
||||
"url": "https://teste-investidor.tempo.co.mz/provincia-de-inhambane-um-destino-estrategico-para-investimento-turistico",
|
||||
"status": 200,
|
||||
"identity_verified": true,
|
||||
"resource_id": 6,
|
||||
"document_sha256": "e90e39e49ba65bbbcbbc303c6eca456e445238daf9acc935ab03690819281678",
|
||||
"current_title": "Província de Inhambane: Um Destino Estratégico para Investimento Turístico",
|
||||
"image_url": "/documentos/27/view",
|
||||
"response_sha256": "a6fbbccb7f9499c5aa0034a3d10810a033c6140f1269bd0074e81f1a5bb8da0c"
|
||||
},
|
||||
"172240": {
|
||||
"url": "https://teste-investidor.tempo.co.mz/maputo-cidade-plataforma-estrategica-de-turismo-negocios-e-investimento-sustentavel",
|
||||
"status": 200,
|
||||
"identity_verified": true,
|
||||
"resource_id": 21,
|
||||
"document_sha256": "8c5e91c937032680883793a12844f1128f7ac99e97c63b68c23d1396e009ba9d",
|
||||
"current_title": "Maputo Cidade – Plataforma Estratégica de Turismo, Negócios e Investimento Sustentável",
|
||||
"image_url": "/documentos/27/view",
|
||||
"response_sha256": "265afdbc2b8997daa69cb519c7409a74d3dd590f7c57b144723786a3c167034d"
|
||||
},
|
||||
"172237": {
|
||||
"url": "https://teste-investidor.tempo.co.mz/provincia-de-maputo-turismo",
|
||||
"status": 200,
|
||||
"identity_verified": true,
|
||||
"resource_id": 22,
|
||||
"document_sha256": "a62a43e31da32795a7286f4c152a93d5dc829233e72023b00ea61fd8e2fcb58c",
|
||||
"current_title": "Província de Maputo: Natureza, Cultura e Conectividade como Motores de Crescimento Sustentável",
|
||||
"image_url": "/documentos/27/view",
|
||||
"response_sha256": "7827fa353f9fc126938732d42fa57353b48c58f448e25f8466665086779860dc"
|
||||
},
|
||||
"172233": {
|
||||
"url": "https://teste-investidor.tempo.co.mz/provincia-de-gaza-oportunidades-mais-solidas-e-estrategicas-de-investimento-no-turismo-no-sul-de-mocambique",
|
||||
"status": 200,
|
||||
"identity_verified": true,
|
||||
"resource_id": 23,
|
||||
"document_sha256": "345dfd72d8ebf459eae8eb025d699afddbae751df55ffa8d053568cd08096c97",
|
||||
"current_title": "Província de Gaza: Oportunidades mais sólidas e estratégicas de investimento no turismo no sul de Moçambique.",
|
||||
"image_url": "/documentos/27/view",
|
||||
"response_sha256": "bfb80dff52964448b4bbd03995706020b9bccaa14d8abc1d944123e6b1240a6f"
|
||||
},
|
||||
"172230": {
|
||||
"url": "https://teste-investidor.tempo.co.mz/provincia-de-nampula-perfil-de-investimento-no-setor-do-turismo",
|
||||
"status": 200,
|
||||
"identity_verified": true,
|
||||
"resource_id": 24,
|
||||
"document_sha256": "4f4570f7bbd5599cb925ed996c1734a23582362fe485736931a53b9266565d4e",
|
||||
"current_title": "Província de Nampula - Perfil de investimento no setor do turismo",
|
||||
"image_url": "/documentos/27/view",
|
||||
"response_sha256": "6d42e0a347c938d201b8c8780fcdf0da83e6dab9fc031ea0ac73b14464f4f696"
|
||||
},
|
||||
"172223": {
|
||||
"url": "https://teste-investidor.tempo.co.mz/cabo-delgado-pemba-a-capital-do-turismo-costeiro-do-norte-de-mocambique",
|
||||
"status": 200,
|
||||
"identity_verified": true,
|
||||
"resource_id": 25,
|
||||
"document_sha256": "514a9d88c097d234038fa6341f5a5b01e8938cb6a9535a807145c84c37010a4d",
|
||||
"current_title": "Cabo Delgado - Pemba, A capital do turismo costeiro do norte de Moçambique",
|
||||
"image_url": "/documentos/27/view",
|
||||
"response_sha256": "a579796f41716bb43444295dce91b19d0346828e7b3cbfc994efbdc5058ec69b"
|
||||
},
|
||||
"172219": {
|
||||
"url": "https://teste-investidor.tempo.co.mz/niassa-o-ultimo-grande-destino-virgem-do-turismo-africano",
|
||||
"status": 200,
|
||||
"identity_verified": true,
|
||||
"resource_id": 26,
|
||||
"document_sha256": "0d468ae0de9a76d2b140bc8cb5c555f29626fb6422287c917762bec3e615a70c",
|
||||
"current_title": "Niassa - O último grande destino virgem do turismo africano",
|
||||
"image_url": "/documentos/27/view",
|
||||
"response_sha256": "cffb6ea36fa8273112edaa31432f9c5093ea8e0303fee83c3cdb396e83aac816"
|
||||
},
|
||||
"171992": {
|
||||
"url": "https://teste-investidor.tempo.co.mz/infraestruturas",
|
||||
"status": 200,
|
||||
"identity_verified": true,
|
||||
"resource_id": 17,
|
||||
"document_sha256": "05eb492dd6cfca6a7f305b633658a16723c315168f4b2c75103f22d1fac84032",
|
||||
"current_title": "Infraestruturas",
|
||||
"image_url": "/documentos/16/view",
|
||||
"response_sha256": "0afd5c42dc0a187a22cb3f8ef8af1b10348d31899ce3b1befbc2c38b379d475b"
|
||||
}
|
||||
}
|
||||
},
|
||||
"qa_import": {
|
||||
"port": 55441,
|
||||
"actor": 239,
|
||||
"resources": [
|
||||
{
|
||||
"id": 43,
|
||||
"title": "15/30 costa do sol com dua na zona de mupulene muito lindo",
|
||||
"published": false,
|
||||
"external_id": "legacy-tempo-post-62413-20170626",
|
||||
"sha256": "b471c1afd7235f359acdcc16a93088f77e1f988706c8d3c4d4f11c5d560aea47",
|
||||
"malware_scan_status": "clean",
|
||||
"malware_scanned_sha256": "b471c1afd7235f359acdcc16a93088f77e1f988706c8d3c4d4f11c5d560aea47",
|
||||
"data_classification": "internal"
|
||||
},
|
||||
{
|
||||
"id": 44,
|
||||
"title": "Banco Mundial classifica de proibitivos juros praticados em Moçambique",
|
||||
"published": false,
|
||||
"external_id": "legacy-tempo-post-73945-20170731",
|
||||
"sha256": "2aa0ce0eb305bfdf93e390a978d3694d0fbc0129034077b68cebce418f878415",
|
||||
"malware_scan_status": "clean",
|
||||
"malware_scanned_sha256": "2aa0ce0eb305bfdf93e390a978d3694d0fbc0129034077b68cebce418f878415",
|
||||
"data_classification": "internal"
|
||||
},
|
||||
{
|
||||
"id": 45,
|
||||
"title": "Coloque o seu número para o grupo de whatsapp, venda de acessórios de computador",
|
||||
"published": false,
|
||||
"external_id": "legacy-tempo-post-76474-20170808",
|
||||
"sha256": "8f769aec4715449d056c8c25294060b2ff290ec0893c28e9bdcc4e8092fc75f5",
|
||||
"malware_scan_status": "clean",
|
||||
"malware_scanned_sha256": "8f769aec4715449d056c8c25294060b2ff290ec0893c28e9bdcc4e8092fc75f5",
|
||||
"data_classification": "internal"
|
||||
},
|
||||
{
|
||||
"id": 46,
|
||||
"title": "Governo pretende construir 100 mil habitações em dois anos",
|
||||
"published": false,
|
||||
"external_id": "legacy-tempo-post-75002-20170804",
|
||||
"sha256": "7b6fbd998be2b47b96e9a4724560c192c502709fa308d6590c0797ddcdb10544",
|
||||
"malware_scan_status": "clean",
|
||||
"malware_scanned_sha256": "7b6fbd998be2b47b96e9a4724560c192c502709fa308d6590c0797ddcdb10544",
|
||||
"data_classification": "internal"
|
||||
},
|
||||
{
|
||||
"id": 47,
|
||||
"title": "Rofino Licuco preso cinco meses depois de condenado por agressão",
|
||||
"published": false,
|
||||
"external_id": "legacy-tempo-post-73246-20170728",
|
||||
"sha256": "430ee8674c71104fc126bed2911d676f150ac623857a1ada75662987fd9ac390",
|
||||
"malware_scan_status": "clean",
|
||||
"malware_scanned_sha256": "430ee8674c71104fc126bed2911d676f150ac623857a1ada75662987fd9ac390",
|
||||
"data_classification": "internal"
|
||||
},
|
||||
{
|
||||
"id": 48,
|
||||
"title": "Sete indivíduos mortos a tiro na Matola",
|
||||
"published": false,
|
||||
"external_id": "legacy-tempo-post-77232-20170811",
|
||||
"sha256": "27544d3127803b7495bef9fd633147d0225d8c31816c4d59ed05a2edd59de070",
|
||||
"malware_scan_status": "clean",
|
||||
"malware_scanned_sha256": "27544d3127803b7495bef9fd633147d0225d8c31816c4d59ed05a2edd59de070",
|
||||
"data_classification": "internal"
|
||||
},
|
||||
{
|
||||
"id": 49,
|
||||
"title": "Daúde Razaque já não é treinador da Liga Desportiva de Maputo",
|
||||
"published": false,
|
||||
"external_id": "legacy-tempo-post-62929-20170627",
|
||||
"sha256": "488017b062c6760659d9147b7f12aea4afd8d824121adb9462e2c7a2ae58437b",
|
||||
"malware_scan_status": "clean",
|
||||
"malware_scanned_sha256": "488017b062c6760659d9147b7f12aea4afd8d824121adb9462e2c7a2ae58437b",
|
||||
"data_classification": "internal"
|
||||
},
|
||||
{
|
||||
"id": 50,
|
||||
"title": "Dois alunos da 9ª classe caíram nas mãos da Polícia da República de Moçambique (...",
|
||||
"published": false,
|
||||
"external_id": "legacy-tempo-post-63248-20170628",
|
||||
"sha256": "6c30e61197b8568332af6d9b19bbba7ad8665b3873bb031a8c8aedd41536b060",
|
||||
"malware_scan_status": "clean",
|
||||
"malware_scanned_sha256": "6c30e61197b8568332af6d9b19bbba7ad8665b3873bb031a8c8aedd41536b060",
|
||||
"data_classification": "internal"
|
||||
},
|
||||
{
|
||||
"id": 51,
|
||||
"title": "Alumínio “Verde”",
|
||||
"published": false,
|
||||
"external_id": "legacy-tempo-post-173220-20260616",
|
||||
"sha256": "5b76c3d84dcf7f9a489686d90e3857ae6536705c918e841310bb42b354e80976",
|
||||
"malware_scan_status": "clean",
|
||||
"malware_scanned_sha256": "5b76c3d84dcf7f9a489686d90e3857ae6536705c918e841310bb42b354e80976",
|
||||
"data_classification": "internal"
|
||||
},
|
||||
{
|
||||
"id": 52,
|
||||
"title": "Checklist para Contratação e Direitos do Trabalho Doméstico",
|
||||
"published": false,
|
||||
"external_id": "legacy-tempo-post-173482-20260915",
|
||||
"sha256": "6f888e133a779c719073246f89bcae150198e5897c0e7d9b7ba75f54c54a7830",
|
||||
"malware_scan_status": "clean",
|
||||
"malware_scanned_sha256": "6f888e133a779c719073246f89bcae150198e5897c0e7d9b7ba75f54c54a7830",
|
||||
"data_classification": "internal"
|
||||
},
|
||||
{
|
||||
"id": 53,
|
||||
"title": "Frelimo e Zanu-PF: Evocados laços de irmandade",
|
||||
"published": false,
|
||||
"external_id": "legacy-tempo-post-62414-20170626",
|
||||
"sha256": "a040c0b7cb95dd7f053f4d13662dd58b2b06453b6fa61a2d531d0257a8db8c17",
|
||||
"malware_scan_status": "clean",
|
||||
"malware_scanned_sha256": "a040c0b7cb95dd7f053f4d13662dd58b2b06453b6fa61a2d531d0257a8db8c17",
|
||||
"data_classification": "internal"
|
||||
},
|
||||
{
|
||||
"id": 54,
|
||||
"title": "Governo dará todo O apoio à PGR no seguimento da auditoria",
|
||||
"published": false,
|
||||
"external_id": "legacy-tempo-post-62418-20170626",
|
||||
"sha256": "e8ccafa68f0d1aa80f2e41d9e09f7b13e7c653a8472a731798cfd5e04ce2963e",
|
||||
"malware_scan_status": "clean",
|
||||
"malware_scanned_sha256": "e8ccafa68f0d1aa80f2e41d9e09f7b13e7c653a8472a731798cfd5e04ce2963e",
|
||||
"data_classification": "internal"
|
||||
},
|
||||
{
|
||||
"id": 55,
|
||||
"title": "Sete pessoas morrem e 150 são resgatados em acidente com eléctrico na Índia",
|
||||
"published": false,
|
||||
"external_id": "legacy-tempo-post-62416-20170626",
|
||||
"sha256": "ac575ce340571192552c89b63f0bd62a84dbc47828e6930133af5ff70e249319",
|
||||
"malware_scan_status": "clean",
|
||||
"malware_scanned_sha256": "ac575ce340571192552c89b63f0bd62a84dbc47828e6930133af5ff70e249319",
|
||||
"data_classification": "internal"
|
||||
},
|
||||
{
|
||||
"id": 56,
|
||||
"title": "Daviz Simango não se incomoda com possível sucessão na Beira",
|
||||
"published": false,
|
||||
"external_id": "legacy-tempo-post-62649-20170626",
|
||||
"sha256": "38a858713472759a2d20a308bdd9d8afa9cfb76dcaf8d11ca709e89261573e42",
|
||||
"malware_scan_status": "clean",
|
||||
"malware_scanned_sha256": "38a858713472759a2d20a308bdd9d8afa9cfb76dcaf8d11ca709e89261573e42",
|
||||
"data_classification": "internal"
|
||||
},
|
||||
{
|
||||
"id": 57,
|
||||
"title": "“Exigimos responsabilização dos agentes do crime”, Daviz Simango",
|
||||
"published": false,
|
||||
"external_id": "legacy-tempo-post-62739-20170626",
|
||||
"sha256": "5870fc0fad9bb7af537b7b1468151cde138b1b886ce846c7be25647b630245dd",
|
||||
"malware_scan_status": "clean",
|
||||
"malware_scanned_sha256": "5870fc0fad9bb7af537b7b1468151cde138b1b886ce846c7be25647b630245dd",
|
||||
"data_classification": "internal"
|
||||
},
|
||||
{
|
||||
"id": 58,
|
||||
"title": "Governo e parceiros reforçam mecanismos de controlo de HIV/Sida",
|
||||
"published": false,
|
||||
"external_id": "legacy-tempo-post-62854-20170627",
|
||||
"sha256": "44b1ba9d593bb625cf53de8909e78eb0b3129742c3ec9d58ec68c984e8930bce",
|
||||
"malware_scan_status": "clean",
|
||||
"malware_scanned_sha256": "44b1ba9d593bb625cf53de8909e78eb0b3129742c3ec9d58ec68c984e8930bce",
|
||||
"data_classification": "internal"
|
||||
},
|
||||
{
|
||||
"id": 59,
|
||||
"title": "Graça Machel diz que desnutrição é um problema de desenvolvimento",
|
||||
"published": false,
|
||||
"external_id": "legacy-tempo-post-63128-20170627",
|
||||
"sha256": "74be8f29e5053f53ab1a39a2f63208145c30f23357ca6d78b637dd02bf9c1fb4",
|
||||
"malware_scan_status": "clean",
|
||||
"malware_scanned_sha256": "74be8f29e5053f53ab1a39a2f63208145c30f23357ca6d78b637dd02bf9c1fb4",
|
||||
"data_classification": "internal"
|
||||
},
|
||||
{
|
||||
"id": 60,
|
||||
"title": "In dependência",
|
||||
"published": false,
|
||||
"external_id": "legacy-tempo-post-63181-20170627",
|
||||
"sha256": "3d0f9ac14d36156d8178e33f8616d6406f31ea4cd69c8308cbf4227586d7ccb3",
|
||||
"malware_scan_status": "clean",
|
||||
"malware_scanned_sha256": "3d0f9ac14d36156d8178e33f8616d6406f31ea4cd69c8308cbf4227586d7ccb3",
|
||||
"data_classification": "internal"
|
||||
},
|
||||
{
|
||||
"id": 61,
|
||||
"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",
|
||||
"published": false,
|
||||
"external_id": "legacy-tempo-post-63146-20170627",
|
||||
"sha256": "542353466cc6d82b651619c495f138b0576a4be41d21aad194ae2005050cb1bc",
|
||||
"malware_scan_status": "clean",
|
||||
"malware_scanned_sha256": "542353466cc6d82b651619c495f138b0576a4be41d21aad194ae2005050cb1bc",
|
||||
"data_classification": "internal"
|
||||
},
|
||||
{
|
||||
"id": 62,
|
||||
"title": "O Instituto Nacional de Meteorologia prevê o seguinte estado do tempo para esta ...",
|
||||
"published": false,
|
||||
"external_id": "legacy-tempo-post-62688-20170626",
|
||||
"sha256": "a210a789b06d425822f4124f74226a2b27246c086c5ec5f041f1b2237d7c2b65",
|
||||
"malware_scan_status": "clean",
|
||||
"malware_scanned_sha256": "a210a789b06d425822f4124f74226a2b27246c086c5ec5f041f1b2237d7c2b65",
|
||||
"data_classification": "internal"
|
||||
},
|
||||
{
|
||||
"id": 63,
|
||||
"title": "Energia e Recursos Naturais",
|
||||
"published": false,
|
||||
"external_id": "legacy-tempo-post-173222-20260616",
|
||||
"sha256": "e64d7063d1ac4dda028c97e1ba64938d2a9e426a0b630c57ee5df52a41312cc7",
|
||||
"malware_scan_status": "clean",
|
||||
"malware_scanned_sha256": "e64d7063d1ac4dda028c97e1ba64938d2a9e426a0b630c57ee5df52a41312cc7",
|
||||
"data_classification": "internal"
|
||||
},
|
||||
{
|
||||
"id": 64,
|
||||
"title": "Direitos Especiais da Trabalhadora Doméstica no Regulamento",
|
||||
"published": false,
|
||||
"external_id": "legacy-tempo-post-173484-20260915",
|
||||
"sha256": "297df455a9c6fd1f0ab7ca7bbe124ce483c86fe55be26baa695f03089b3ac049",
|
||||
"malware_scan_status": "clean",
|
||||
"malware_scanned_sha256": "297df455a9c6fd1f0ab7ca7bbe124ce483c86fe55be26baa695f03089b3ac049",
|
||||
"data_classification": "internal"
|
||||
},
|
||||
{
|
||||
"id": 65,
|
||||
"title": "Guia Prático para Empregadores sobre Contratação e Direitos no Trabalho Doméstico",
|
||||
"published": false,
|
||||
"external_id": "legacy-tempo-post-173478-20260915",
|
||||
"sha256": "d4e320ed99dd9d53d205e47932f73651976d43081d5d39f2bfc2996901408644",
|
||||
"malware_scan_status": "clean",
|
||||
"malware_scanned_sha256": "d4e320ed99dd9d53d205e47932f73651976d43081d5d39f2bfc2996901408644",
|
||||
"data_classification": "internal"
|
||||
},
|
||||
{
|
||||
"id": 66,
|
||||
"title": "Modelo de Contrato de Trabalho Doméstico para Pequenas Famílias",
|
||||
"published": false,
|
||||
"external_id": "legacy-tempo-post-173480-20260915",
|
||||
"sha256": "c3ee6401eda5b582c61969b5e3b74fdf26ef34ca481b1aca8c4c309d845147ae",
|
||||
"malware_scan_status": "clean",
|
||||
"malware_scanned_sha256": "c3ee6401eda5b582c61969b5e3b74fdf26ef34ca481b1aca8c4c309d845147ae",
|
||||
"data_classification": "internal"
|
||||
},
|
||||
{
|
||||
"id": 67,
|
||||
"title": "Procedimentos para Rescisão do Contrato de Trabalho Doméstico segundo o Regulamento de Moçambique",
|
||||
"published": false,
|
||||
"external_id": "legacy-tempo-post-173476-20260915",
|
||||
"sha256": "72fbf28391db4804e0637b64e7f59e9fa597812a2681b4562dc3d6dfb4093a30",
|
||||
"malware_scan_status": "clean",
|
||||
"malware_scanned_sha256": "72fbf28391db4804e0637b64e7f59e9fa597812a2681b4562dc3d6dfb4093a30",
|
||||
"data_classification": "internal"
|
||||
},
|
||||
{
|
||||
"id": 68,
|
||||
"title": "Direitos e Deveres do Trabalhador Doméstico",
|
||||
"published": false,
|
||||
"external_id": "legacy-tempo-post-173470-20260915",
|
||||
"sha256": "26feeae6b8d04972c7a64ee360583cccdcaeb4b0d8795fd7bc2fd3c38866cc19",
|
||||
"malware_scan_status": "clean",
|
||||
"malware_scanned_sha256": "26feeae6b8d04972c7a64ee360583cccdcaeb4b0d8795fd7bc2fd3c38866cc19",
|
||||
"data_classification": "internal"
|
||||
},
|
||||
{
|
||||
"id": 69,
|
||||
"title": "Sanções Disciplinares Aplicáveis a Trabalhadores Domésticos segundo o Regulamento de Moçambique",
|
||||
"published": false,
|
||||
"external_id": "legacy-tempo-post-173474-20260915",
|
||||
"sha256": "9fa741a17b746bfa2f3ce7b6ce1d2bb34d3a35d3b58330b0554f9e0a6844f049",
|
||||
"malware_scan_status": "clean",
|
||||
"malware_scanned_sha256": "9fa741a17b746bfa2f3ce7b6ce1d2bb34d3a35d3b58330b0554f9e0a6844f049",
|
||||
"data_classification": "internal"
|
||||
},
|
||||
{
|
||||
"id": 70,
|
||||
"title": "Regulamento do Trabalho Doméstico",
|
||||
"published": false,
|
||||
"external_id": "legacy-tempo-post-173463-20260915",
|
||||
"sha256": "4f34b7bac4b3030c0614844d379f6e3cfad1b7f6ea3db575a58c33ae033b0e73",
|
||||
"malware_scan_status": "clean",
|
||||
"malware_scanned_sha256": "4f34b7bac4b3030c0614844d379f6e3cfad1b7f6ea3db575a58c33ae033b0e73",
|
||||
"data_classification": "internal"
|
||||
}
|
||||
],
|
||||
"imported_private": 28,
|
||||
"clean_actual_scans": 28,
|
||||
"idempotent_replay_reused": 28,
|
||||
"existing_resources_preserved": true,
|
||||
"roles_unchanged": true,
|
||||
"new_publications": 0,
|
||||
"active_writes": 0
|
||||
},
|
||||
"original_gaps": [
|
||||
{
|
||||
"legacy_id": 171994,
|
||||
"url": "https://investemoz.tempo.co.mz/mercado-financeiro",
|
||||
"status": 200,
|
||||
"title": "Mercado Financeiro",
|
||||
"body_characters": 0,
|
||||
"body_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"same_body_as_capture": true
|
||||
},
|
||||
{
|
||||
"legacy_id": 74364,
|
||||
"url": "https://investemoz.tempo.co.mz/74364-2",
|
||||
"status": 200,
|
||||
"title": "",
|
||||
"body_characters": 274,
|
||||
"body_sha256": "2a7f0a5b09e0136fec9eae357ac8a88a159ead23b25ed7bb735c75997b10ec5e",
|
||||
"same_body_as_capture": true
|
||||
}
|
||||
],
|
||||
"educational_cards": {
|
||||
"in_main": true,
|
||||
"observed_url": "https://teste-investidor.tempo.co.mz/explorar/turismo",
|
||||
"read_guide_links": 12,
|
||||
"visual_acceptance_by_tempo": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
{
|
||||
"qa_only": true,
|
||||
"originals": 28,
|
||||
"rows": [
|
||||
{
|
||||
"external_id": "legacy-tempo-post-62413-20170626",
|
||||
"qa_resource_id": 43,
|
||||
"version": 2,
|
||||
"original_sha256": "b471c1afd7235f359acdcc16a93088f77e1f988706c8d3c4d4f11c5d560aea47",
|
||||
"private": true,
|
||||
"image_available": false,
|
||||
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
|
||||
"body_image_references": 1,
|
||||
"history_revisions": 2
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-73945-20170731",
|
||||
"qa_resource_id": 44,
|
||||
"version": 1,
|
||||
"original_sha256": "2aa0ce0eb305bfdf93e390a978d3694d0fbc0129034077b68cebce418f878415",
|
||||
"private": true,
|
||||
"image_available": false,
|
||||
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
|
||||
"body_image_references": 1,
|
||||
"history_revisions": 1
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-76474-20170808",
|
||||
"qa_resource_id": 45,
|
||||
"version": 1,
|
||||
"original_sha256": "8f769aec4715449d056c8c25294060b2ff290ec0893c28e9bdcc4e8092fc75f5",
|
||||
"private": true,
|
||||
"image_available": false,
|
||||
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
|
||||
"body_image_references": 2,
|
||||
"history_revisions": 1
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-62414-20170626",
|
||||
"qa_resource_id": 53,
|
||||
"version": 1,
|
||||
"original_sha256": "a040c0b7cb95dd7f053f4d13662dd58b2b06453b6fa61a2d531d0257a8db8c17",
|
||||
"private": true,
|
||||
"image_available": false,
|
||||
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
|
||||
"body_image_references": 1,
|
||||
"history_revisions": 1
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-62418-20170626",
|
||||
"qa_resource_id": 54,
|
||||
"version": 1,
|
||||
"original_sha256": "e8ccafa68f0d1aa80f2e41d9e09f7b13e7c653a8472a731798cfd5e04ce2963e",
|
||||
"private": true,
|
||||
"image_available": false,
|
||||
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
|
||||
"body_image_references": 1,
|
||||
"history_revisions": 1
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-75002-20170804",
|
||||
"qa_resource_id": 46,
|
||||
"version": 1,
|
||||
"original_sha256": "7b6fbd998be2b47b96e9a4724560c192c502709fa308d6590c0797ddcdb10544",
|
||||
"private": true,
|
||||
"image_available": false,
|
||||
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
|
||||
"body_image_references": 1,
|
||||
"history_revisions": 1
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-73246-20170728",
|
||||
"qa_resource_id": 47,
|
||||
"version": 1,
|
||||
"original_sha256": "430ee8674c71104fc126bed2911d676f150ac623857a1ada75662987fd9ac390",
|
||||
"private": true,
|
||||
"image_available": false,
|
||||
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
|
||||
"body_image_references": 1,
|
||||
"history_revisions": 1
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-77232-20170811",
|
||||
"qa_resource_id": 48,
|
||||
"version": 1,
|
||||
"original_sha256": "27544d3127803b7495bef9fd633147d0225d8c31816c4d59ed05a2edd59de070",
|
||||
"private": true,
|
||||
"image_available": false,
|
||||
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
|
||||
"body_image_references": 1,
|
||||
"history_revisions": 1
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-62416-20170626",
|
||||
"qa_resource_id": 55,
|
||||
"version": 1,
|
||||
"original_sha256": "ac575ce340571192552c89b63f0bd62a84dbc47828e6930133af5ff70e249319",
|
||||
"private": true,
|
||||
"image_available": false,
|
||||
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
|
||||
"body_image_references": 0,
|
||||
"history_revisions": 1
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-62929-20170627",
|
||||
"qa_resource_id": 49,
|
||||
"version": 1,
|
||||
"original_sha256": "488017b062c6760659d9147b7f12aea4afd8d824121adb9462e2c7a2ae58437b",
|
||||
"private": true,
|
||||
"image_available": false,
|
||||
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
|
||||
"body_image_references": 1,
|
||||
"history_revisions": 1
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-62649-20170626",
|
||||
"qa_resource_id": 56,
|
||||
"version": 1,
|
||||
"original_sha256": "38a858713472759a2d20a308bdd9d8afa9cfb76dcaf8d11ca709e89261573e42",
|
||||
"private": true,
|
||||
"image_available": false,
|
||||
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
|
||||
"body_image_references": 1,
|
||||
"history_revisions": 1
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-62739-20170626",
|
||||
"qa_resource_id": 57,
|
||||
"version": 1,
|
||||
"original_sha256": "5870fc0fad9bb7af537b7b1468151cde138b1b886ce846c7be25647b630245dd",
|
||||
"private": true,
|
||||
"image_available": false,
|
||||
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
|
||||
"body_image_references": 1,
|
||||
"history_revisions": 1
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-62854-20170627",
|
||||
"qa_resource_id": 58,
|
||||
"version": 1,
|
||||
"original_sha256": "44b1ba9d593bb625cf53de8909e78eb0b3129742c3ec9d58ec68c984e8930bce",
|
||||
"private": true,
|
||||
"image_available": false,
|
||||
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
|
||||
"body_image_references": 1,
|
||||
"history_revisions": 1
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-63128-20170627",
|
||||
"qa_resource_id": 59,
|
||||
"version": 1,
|
||||
"original_sha256": "74be8f29e5053f53ab1a39a2f63208145c30f23357ca6d78b637dd02bf9c1fb4",
|
||||
"private": true,
|
||||
"image_available": false,
|
||||
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
|
||||
"body_image_references": 1,
|
||||
"history_revisions": 1
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-63181-20170627",
|
||||
"qa_resource_id": 60,
|
||||
"version": 1,
|
||||
"original_sha256": "3d0f9ac14d36156d8178e33f8616d6406f31ea4cd69c8308cbf4227586d7ccb3",
|
||||
"private": true,
|
||||
"image_available": false,
|
||||
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
|
||||
"body_image_references": 1,
|
||||
"history_revisions": 1
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-63146-20170627",
|
||||
"qa_resource_id": 61,
|
||||
"version": 1,
|
||||
"original_sha256": "542353466cc6d82b651619c495f138b0576a4be41d21aad194ae2005050cb1bc",
|
||||
"private": true,
|
||||
"image_available": false,
|
||||
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
|
||||
"body_image_references": 1,
|
||||
"history_revisions": 1
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-62688-20170626",
|
||||
"qa_resource_id": 62,
|
||||
"version": 1,
|
||||
"original_sha256": "a210a789b06d425822f4124f74226a2b27246c086c5ec5f041f1b2237d7c2b65",
|
||||
"private": true,
|
||||
"image_available": false,
|
||||
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
|
||||
"body_image_references": 0,
|
||||
"history_revisions": 1
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-63248-20170628",
|
||||
"qa_resource_id": 50,
|
||||
"version": 1,
|
||||
"original_sha256": "6c30e61197b8568332af6d9b19bbba7ad8665b3873bb031a8c8aedd41536b060",
|
||||
"private": true,
|
||||
"image_available": false,
|
||||
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
|
||||
"body_image_references": 0,
|
||||
"history_revisions": 1
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-173220-20260616",
|
||||
"qa_resource_id": 51,
|
||||
"version": 1,
|
||||
"original_sha256": "5b76c3d84dcf7f9a489686d90e3857ae6536705c918e841310bb42b354e80976",
|
||||
"private": true,
|
||||
"image_available": false,
|
||||
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
|
||||
"body_image_references": 0,
|
||||
"history_revisions": 1
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-173222-20260616",
|
||||
"qa_resource_id": 63,
|
||||
"version": 1,
|
||||
"original_sha256": "e64d7063d1ac4dda028c97e1ba64938d2a9e426a0b630c57ee5df52a41312cc7",
|
||||
"private": true,
|
||||
"image_available": false,
|
||||
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
|
||||
"body_image_references": 0,
|
||||
"history_revisions": 1
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-173482-20260915",
|
||||
"qa_resource_id": 52,
|
||||
"version": 1,
|
||||
"original_sha256": "6f888e133a779c719073246f89bcae150198e5897c0e7d9b7ba75f54c54a7830",
|
||||
"private": true,
|
||||
"image_available": false,
|
||||
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
|
||||
"body_image_references": 0,
|
||||
"history_revisions": 1
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-173484-20260915",
|
||||
"qa_resource_id": 64,
|
||||
"version": 1,
|
||||
"original_sha256": "297df455a9c6fd1f0ab7ca7bbe124ce483c86fe55be26baa695f03089b3ac049",
|
||||
"private": true,
|
||||
"image_available": false,
|
||||
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
|
||||
"body_image_references": 0,
|
||||
"history_revisions": 1
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-173478-20260915",
|
||||
"qa_resource_id": 65,
|
||||
"version": 1,
|
||||
"original_sha256": "d4e320ed99dd9d53d205e47932f73651976d43081d5d39f2bfc2996901408644",
|
||||
"private": true,
|
||||
"image_available": false,
|
||||
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
|
||||
"body_image_references": 0,
|
||||
"history_revisions": 1
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-173480-20260915",
|
||||
"qa_resource_id": 66,
|
||||
"version": 1,
|
||||
"original_sha256": "c3ee6401eda5b582c61969b5e3b74fdf26ef34ca481b1aca8c4c309d845147ae",
|
||||
"private": true,
|
||||
"image_available": false,
|
||||
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
|
||||
"body_image_references": 0,
|
||||
"history_revisions": 1
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-173476-20260915",
|
||||
"qa_resource_id": 67,
|
||||
"version": 1,
|
||||
"original_sha256": "72fbf28391db4804e0637b64e7f59e9fa597812a2681b4562dc3d6dfb4093a30",
|
||||
"private": true,
|
||||
"image_available": false,
|
||||
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
|
||||
"body_image_references": 0,
|
||||
"history_revisions": 1
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-173470-20260915",
|
||||
"qa_resource_id": 68,
|
||||
"version": 1,
|
||||
"original_sha256": "26feeae6b8d04972c7a64ee360583cccdcaeb4b0d8795fd7bc2fd3c38866cc19",
|
||||
"private": true,
|
||||
"image_available": false,
|
||||
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
|
||||
"body_image_references": 0,
|
||||
"history_revisions": 1
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-173474-20260915",
|
||||
"qa_resource_id": 69,
|
||||
"version": 1,
|
||||
"original_sha256": "9fa741a17b746bfa2f3ce7b6ce1d2bb34d3a35d3b58330b0554f9e0a6844f049",
|
||||
"private": true,
|
||||
"image_available": false,
|
||||
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
|
||||
"body_image_references": 0,
|
||||
"history_revisions": 1
|
||||
},
|
||||
{
|
||||
"external_id": "legacy-tempo-post-173463-20260915",
|
||||
"qa_resource_id": 70,
|
||||
"version": 1,
|
||||
"original_sha256": "4f34b7bac4b3030c0614844d379f6e3cfad1b7f6ea3db575a58c33ae033b0e73",
|
||||
"private": true,
|
||||
"image_available": false,
|
||||
"image_reference": "https://investemoz.tempo.co.mz/images/tempo-background.png",
|
||||
"body_image_references": 0,
|
||||
"history_revisions": 1
|
||||
}
|
||||
],
|
||||
"archive_entries": 57,
|
||||
"archive_sha256": "177c50d1cc3b1610e62123a2f8b532711a15f875aac2ee0e372636c2f747e8a2",
|
||||
"archive_bytes": 85614,
|
||||
"verified_original_hashes": 28,
|
||||
"included_image_originals": 0,
|
||||
"destination_imported": false,
|
||||
"destination_published": false,
|
||||
"database_writes": 0,
|
||||
"tests": {
|
||||
"passed": 6,
|
||||
"assertions": 1000,
|
||||
"database": "simulator_fiscal_delta_20261021_test",
|
||||
"port": 55441,
|
||||
"publication_transactions_rolled_back": true,
|
||||
"all_28_sanitized_original_text_preserved": true
|
||||
},
|
||||
"typescript": "passed",
|
||||
"qa_build": "passed",
|
||||
"public20_rechecked": false,
|
||||
"reimported": false,
|
||||
"main_integrated": false,
|
||||
"user_review_claimed": false,
|
||||
"ui_saved_resource": 43,
|
||||
"ui_saved_version": 2,
|
||||
"original_image_sha256": "28f41f12e10ab34545d8e432f6e8c74f86960e17e0c4a9956f514bcde4881eaa"
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
+82
-272
@@ -7,66 +7,34 @@
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@import './tempo-header.css';
|
||||
|
||||
/* Institutional shell adapted from Tempo; page content keeps its own theme. */
|
||||
.tempo-header {
|
||||
position: relative;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 40;
|
||||
background: #090909;
|
||||
color: #fff;
|
||||
}
|
||||
.tempo-header-inner {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto auto;
|
||||
grid-template-rows: 72px;
|
||||
column-gap: 0.5rem;
|
||||
align-items: center;
|
||||
max-width: 1440px;
|
||||
margin-inline: auto;
|
||||
padding-inline: 0.75rem;
|
||||
grid-template-rows: 84px;
|
||||
}
|
||||
.tempo-logo-link {
|
||||
grid-area: 1 / 1;
|
||||
display: inline-flex;
|
||||
width: fit-content;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
width: 140px;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
.tempo-header-logo {
|
||||
width: auto;
|
||||
height: 36px;
|
||||
object-fit: contain;
|
||||
}
|
||||
.tempo-primary-navigation {
|
||||
display: none;
|
||||
grid-area: 2 / 1 / auto / -1;
|
||||
min-width: 0;
|
||||
border-top: 1px solid #ffffff1a;
|
||||
}
|
||||
.tempo-primary-navigation > ul {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 0.25rem 0.5rem;
|
||||
padding-block: 0.75rem;
|
||||
}
|
||||
.tempo-primary-navigation .tempo-nav-trigger {
|
||||
font-size: 0.9375rem;
|
||||
padding-inline: 0.5rem;
|
||||
}
|
||||
.tempo-nav-trigger {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-height: 44px;
|
||||
min-height: 48px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.4rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: 0.4rem;
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
gap: 0.5rem;
|
||||
padding: 0.6rem 0.75rem;
|
||||
border-radius: 0.5rem;
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
color: #eee;
|
||||
transition:
|
||||
color 0.2s,
|
||||
@@ -84,85 +52,16 @@
|
||||
.tempo-nav-trigger[data-state='open'] > svg {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
.tempo-primary-navigation .tempo-nav-trigger > svg {
|
||||
transform: none;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.tempo-section-bar {
|
||||
border-top: 1px solid #ffffff14;
|
||||
background: inherit;
|
||||
}
|
||||
.tempo-section-inner {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 0.25rem 0.75rem;
|
||||
max-width: 1440px;
|
||||
margin-inline: auto;
|
||||
padding: 0 0.75rem 0.5rem;
|
||||
}
|
||||
.tempo-section-navigation {
|
||||
grid-area: 1 / 1 / auto / -1;
|
||||
min-width: 0;
|
||||
}
|
||||
.tempo-section-navigation > ul {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
.tempo-section-navigation .tempo-nav-trigger {
|
||||
justify-content: center;
|
||||
min-height: 44px;
|
||||
gap: 0.25rem;
|
||||
padding: 0.5rem 0.375rem;
|
||||
border-radius: 0;
|
||||
font-size: 0.8125rem;
|
||||
letter-spacing: normal;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.tempo-section-navigation .tempo-nav-trigger {
|
||||
min-height: 48px;
|
||||
gap: 0.5rem;
|
||||
padding-inline: 0.75rem;
|
||||
font-size: 1rem;
|
||||
@media (max-width: 640px) {
|
||||
.tempo-nav-trigger {
|
||||
gap: 0.25rem;
|
||||
padding-inline: 0.25rem;
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.tempo-section-inner {
|
||||
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
|
||||
padding: 0.25rem 1.25rem;
|
||||
.tempo-nav-trigger > svg {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
.tempo-section-navigation {
|
||||
grid-area: 1 / 2;
|
||||
}
|
||||
.tempo-section-inner .tempo-panel-summary {
|
||||
grid-area: 1 / 1;
|
||||
}
|
||||
.tempo-section-inner .tempo-interest-button {
|
||||
grid-area: 1 / 3;
|
||||
}
|
||||
}
|
||||
.tempo-header-extras {
|
||||
display: none;
|
||||
grid-area: 3 / 1 / auto / -1;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
min-width: 0;
|
||||
padding-block: 0.75rem;
|
||||
border-top: 1px solid #ffffff1a;
|
||||
}
|
||||
.tempo-header[data-expanded] .tempo-primary-navigation {
|
||||
display: block;
|
||||
}
|
||||
.tempo-header[data-expanded] .tempo-header-extras {
|
||||
display: flex;
|
||||
}
|
||||
.tempo-header-utilities {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.tempo-utility-button {
|
||||
display: flex;
|
||||
@@ -171,7 +70,7 @@
|
||||
gap: 0.4rem;
|
||||
padding: 0.4rem;
|
||||
border-radius: 0.5rem;
|
||||
font-size: 0.8125rem;
|
||||
font-size: 0.75rem;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
.tempo-utility-button:hover {
|
||||
@@ -193,6 +92,11 @@
|
||||
transform: translateY(-2px);
|
||||
opacity: 0.8;
|
||||
}
|
||||
@media (max-width: 360px) {
|
||||
.tempo-header-social {
|
||||
width: 26px;
|
||||
}
|
||||
}
|
||||
.tempo-login-button {
|
||||
display: inline-flex;
|
||||
min-height: 40px;
|
||||
@@ -200,10 +104,9 @@
|
||||
gap: 0.5rem;
|
||||
border: 1px solid #ffffff50;
|
||||
border-radius: 0.4rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
padding: 0.5rem 0.85rem;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
color: #fff;
|
||||
transition:
|
||||
background 0.2s,
|
||||
@@ -213,23 +116,19 @@
|
||||
background: #ed1c24;
|
||||
border-color: #ed1c24;
|
||||
}
|
||||
.tempo-login-button > span {
|
||||
max-width: clamp(2rem, calc(100vw - 18rem), 6rem);
|
||||
}
|
||||
.tempo-header-actions {
|
||||
grid-area: 1 / 2;
|
||||
order: 2;
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-left: auto;
|
||||
}
|
||||
.tempo-interest-button {
|
||||
display: inline-flex;
|
||||
grid-area: 2 / 2;
|
||||
justify-self: end;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 40px;
|
||||
padding: 0.5rem 0.65rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border: 1px solid #ed1c24;
|
||||
border-radius: 0.4rem;
|
||||
background: #ed1c24;
|
||||
@@ -243,171 +142,81 @@
|
||||
background: #c9151c;
|
||||
}
|
||||
.tempo-panel-summary {
|
||||
grid-area: 2 / 1;
|
||||
order: 4;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
gap: 0.1rem;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
width: 100%;
|
||||
min-height: 44px;
|
||||
font-size: 0.8125rem;
|
||||
line-height: 1.4;
|
||||
padding-block: 0.5rem;
|
||||
border-top: 1px solid #ffffff1a;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.tempo-panel-summary a {
|
||||
color: #fff;
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
.tempo-panel-summary:hover {
|
||||
.tempo-panel-summary a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.tempo-panel-count {
|
||||
.tempo-panel-summary span {
|
||||
color: #cbd5e1;
|
||||
font-size: 0.6875rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.tempo-header-toggle {
|
||||
grid-area: 1 / 3;
|
||||
display: inline-flex;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid #ffffff33;
|
||||
border-radius: 0.4rem;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
.tempo-header-toggle:hover,
|
||||
.tempo-header-toggle[aria-expanded='true'] {
|
||||
background: #ffffff10;
|
||||
@media (max-width: 480px) {
|
||||
.tempo-header-actions {
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
padding-block: 0.65rem;
|
||||
}
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.tempo-header-inner {
|
||||
padding-inline: 1.25rem;
|
||||
grid-template-rows: 90px;
|
||||
}
|
||||
.tempo-header-logo {
|
||||
height: 44px;
|
||||
}
|
||||
.tempo-primary-navigation > ul {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.tempo-header-inner {
|
||||
grid-template-rows: 88px;
|
||||
padding-inline: 1.5rem;
|
||||
}
|
||||
.tempo-header-logo {
|
||||
height: 56px;
|
||||
.tempo-logo-link {
|
||||
width: 160px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
/* Shared columns keep each portal area below its institutional link. */
|
||||
.tempo-header-layout {
|
||||
display: grid;
|
||||
grid-template-columns: auto repeat(4, max-content) minmax(0, 1fr) auto;
|
||||
column-gap: 0.25rem;
|
||||
max-width: 1440px;
|
||||
margin-inline: auto;
|
||||
padding-inline: 2rem;
|
||||
}
|
||||
.tempo-header-inner {
|
||||
grid-column: 1 / -1;
|
||||
grid-template-columns: subgrid;
|
||||
min-height: 88px;
|
||||
column-gap: normal;
|
||||
max-width: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
grid-template-rows: 96px;
|
||||
}
|
||||
.tempo-logo-link {
|
||||
margin-right: 0.75rem;
|
||||
}
|
||||
.tempo-header-logo {
|
||||
height: 64px;
|
||||
}
|
||||
.tempo-header[data-expanded] .tempo-primary-navigation,
|
||||
.tempo-primary-navigation {
|
||||
display: grid;
|
||||
grid-area: 1 / 2 / 2 / 6;
|
||||
grid-template-columns: subgrid;
|
||||
border: 0;
|
||||
}
|
||||
.tempo-primary-navigation > ul {
|
||||
display: grid;
|
||||
grid-column: 1 / -1;
|
||||
grid-template-columns: subgrid;
|
||||
gap: normal;
|
||||
padding: 0;
|
||||
}
|
||||
.tempo-nav-trigger {
|
||||
justify-content: center;
|
||||
padding-inline: 0.875rem;
|
||||
font-size: 1.125rem;
|
||||
letter-spacing: 0.025em;
|
||||
}
|
||||
.tempo-primary-navigation .tempo-nav-trigger {
|
||||
padding-inline: 0.625rem;
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
.tempo-header-extras {
|
||||
display: flex;
|
||||
grid-area: 1 / 6;
|
||||
justify-self: end;
|
||||
flex-shrink: 0;
|
||||
padding: 0 0.75rem;
|
||||
border-top: 0;
|
||||
border-inline: 1px solid #ffffff1a;
|
||||
}
|
||||
.tempo-header-utilities {
|
||||
flex-wrap: nowrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.tempo-header-social {
|
||||
width: 26px;
|
||||
}
|
||||
.tempo-header-toggle {
|
||||
display: none;
|
||||
width: 152px;
|
||||
}
|
||||
.tempo-header-actions {
|
||||
grid-area: 1 / 7;
|
||||
order: 6;
|
||||
margin-left: 0;
|
||||
}
|
||||
.tempo-section-bar {
|
||||
display: grid;
|
||||
grid-column: 1 / -1;
|
||||
grid-template-columns: subgrid;
|
||||
.tempo-header-inner .tempo-nav-trigger {
|
||||
padding-inline: 0.5rem;
|
||||
font-size: 0.9375rem;
|
||||
}
|
||||
.tempo-section-inner {
|
||||
grid-column: 1 / -1;
|
||||
grid-template-columns: subgrid;
|
||||
gap: normal;
|
||||
max-width: none;
|
||||
margin: 0;
|
||||
padding-inline: 0;
|
||||
.tempo-panel-summary {
|
||||
order: 0;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
gap: 0.1rem;
|
||||
width: auto;
|
||||
min-width: 102px;
|
||||
border-top: 0;
|
||||
border-left: 1px solid #ffffff1a;
|
||||
padding-left: 0.75rem;
|
||||
}
|
||||
.tempo-section-navigation {
|
||||
display: grid;
|
||||
grid-area: 1 / 2 / 2 / 5;
|
||||
grid-template-columns: subgrid;
|
||||
}
|
||||
@media (min-width: 1440px) {
|
||||
.tempo-header-inner {
|
||||
grid-template-rows: 102px;
|
||||
}
|
||||
.tempo-section-navigation > ul {
|
||||
display: grid;
|
||||
grid-column: 1 / -1;
|
||||
grid-template-columns: subgrid;
|
||||
gap: normal;
|
||||
.tempo-logo-link {
|
||||
width: 168px;
|
||||
}
|
||||
.tempo-section-navigation .tempo-nav-trigger {
|
||||
padding-inline: 0.625rem;
|
||||
}
|
||||
.tempo-primary-navigation .tempo-nav-trigger,
|
||||
.tempo-section-navigation .tempo-nav-trigger {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.tempo-interest-button {
|
||||
padding-inline: 0.85rem;
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
.tempo-section-inner .tempo-interest-button {
|
||||
grid-area: 1 / 6 / 2 / -1;
|
||||
.tempo-header-inner .tempo-nav-trigger {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
.tempo-header :is(a, button):focus-visible {
|
||||
@@ -441,7 +250,7 @@
|
||||
outline: none;
|
||||
}
|
||||
.tempo-footer {
|
||||
margin-top: 2rem;
|
||||
margin-top: 1.5rem;
|
||||
background: #050505;
|
||||
color: #fff;
|
||||
}
|
||||
@@ -475,7 +284,8 @@
|
||||
.tempo-contact-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
gap: 0.75rem;
|
||||
min-height: 40px;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
.tempo-contact-link:hover {
|
||||
|
||||
@@ -0,0 +1,321 @@
|
||||
/* Presentation choice for Tempo's 21/10/2026 request: one shared navigation. */
|
||||
.tempo-header {
|
||||
position: relative;
|
||||
z-index: 40;
|
||||
background: #090909;
|
||||
color: #fff;
|
||||
border-bottom: 1px solid #ffffff14;
|
||||
}
|
||||
.tempo-header-inner {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto auto;
|
||||
grid-template-rows: 71px;
|
||||
align-items: center;
|
||||
column-gap: 0.5rem;
|
||||
max-width: 1600px;
|
||||
margin-inline: auto;
|
||||
padding-inline: 0.75rem;
|
||||
}
|
||||
.tempo-logo-link {
|
||||
grid-area: 1 / 1;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
width: 112px;
|
||||
max-width: 100%;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
.tempo-header-logo {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
.tempo-primary-navigation {
|
||||
display: none;
|
||||
grid-area: 2 / 1 / auto / -1;
|
||||
min-width: 0;
|
||||
border-top: 1px solid #ffffff1a;
|
||||
padding-block: 0.75rem;
|
||||
}
|
||||
.tempo-primary-navigation > ul {
|
||||
display: grid;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
.tempo-nav-trigger {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.4rem;
|
||||
width: 100%;
|
||||
min-height: 44px;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: 0.5rem;
|
||||
color: #e2e8f0;
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
transition:
|
||||
color 0.2s,
|
||||
background 0.2s;
|
||||
}
|
||||
.tempo-nav-trigger:hover,
|
||||
.tempo-nav-trigger[data-state='open'] {
|
||||
background: #ffffff0d;
|
||||
color: #fff;
|
||||
}
|
||||
.tempo-nav-trigger[data-current-group] {
|
||||
color: #fff;
|
||||
background: #ffffff08;
|
||||
box-shadow: inset 0 -2px #ed1c24;
|
||||
}
|
||||
.tempo-nav-trigger > svg {
|
||||
flex-shrink: 0;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
.tempo-nav-trigger[data-state='open'] > svg {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
.tempo-header-extras {
|
||||
display: none;
|
||||
grid-area: 3 / 1 / auto / -1;
|
||||
min-width: 0;
|
||||
padding-block: 0.75rem;
|
||||
border-top: 1px solid #ffffff1a;
|
||||
}
|
||||
.tempo-header-utilities {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.tempo-utility-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
min-height: 44px;
|
||||
padding: 0.4rem;
|
||||
border-radius: 0.5rem;
|
||||
color: #e2e8f0;
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
.tempo-utility-button:hover {
|
||||
background: #ffffff10;
|
||||
}
|
||||
.tempo-header-actions {
|
||||
grid-area: 1 / 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.tempo-login-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
min-height: 44px;
|
||||
padding: 0.5rem 0.625rem;
|
||||
border: 1px solid #ffffff40;
|
||||
border-radius: 0.5rem;
|
||||
color: #fff;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
transition:
|
||||
background 0.2s,
|
||||
border-color 0.2s;
|
||||
}
|
||||
.tempo-login-button:hover {
|
||||
background: #ffffff10;
|
||||
border-color: #ffffff80;
|
||||
}
|
||||
.tempo-login-button > span {
|
||||
max-width: clamp(2rem, calc(100vw - 18rem), 6rem);
|
||||
}
|
||||
.tempo-panel-summary {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 0.5rem;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
.tempo-panel-summary:hover {
|
||||
background: #ffffff10;
|
||||
color: #fff;
|
||||
}
|
||||
.tempo-panel-count {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: -0.125rem;
|
||||
min-width: 17px;
|
||||
padding-inline: 0.2rem;
|
||||
border: 2px solid #090909;
|
||||
border-radius: 999px;
|
||||
background: #ed1c24;
|
||||
color: #fff;
|
||||
font-size: 0.5625rem;
|
||||
line-height: 1.4;
|
||||
text-align: center;
|
||||
}
|
||||
.tempo-interest-button {
|
||||
display: none;
|
||||
grid-area: 4 / 1 / auto / -1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
min-height: 44px;
|
||||
margin-bottom: 1rem;
|
||||
padding: 0.5rem 0.875rem;
|
||||
border: 1px solid #ed1c24;
|
||||
border-radius: 0.5rem;
|
||||
background: #ed1c24;
|
||||
color: #fff;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.tempo-interest-button:hover {
|
||||
background: #c9151c;
|
||||
}
|
||||
.tempo-header-toggle {
|
||||
grid-area: 1 / 3;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border: 1px solid #ffffff33;
|
||||
border-radius: 0.5rem;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
.tempo-header-toggle:hover,
|
||||
.tempo-header-toggle[aria-expanded='true'] {
|
||||
background: #ffffff10;
|
||||
}
|
||||
.tempo-header[data-expanded] .tempo-primary-navigation {
|
||||
display: block;
|
||||
}
|
||||
.tempo-header[data-expanded] .tempo-header-extras {
|
||||
display: flex;
|
||||
}
|
||||
.tempo-header[data-expanded] .tempo-interest-button {
|
||||
display: inline-flex;
|
||||
}
|
||||
.tempo-header :is(a, button):focus-visible {
|
||||
outline: 2px solid #fff;
|
||||
outline-offset: 3px;
|
||||
}
|
||||
.tempo-submenu {
|
||||
max-width: min(
|
||||
calc(100vw - 1.5rem),
|
||||
var(--radix-dropdown-menu-content-available-width)
|
||||
);
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.tempo-header-inner {
|
||||
grid-template-columns: minmax(0, 1fr) auto auto auto;
|
||||
column-gap: 0.75rem;
|
||||
padding-inline: 1.5rem;
|
||||
}
|
||||
.tempo-logo-link {
|
||||
width: 132px;
|
||||
}
|
||||
.tempo-interest-button {
|
||||
display: inline-flex;
|
||||
grid-area: 1 / 3;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.tempo-header-toggle {
|
||||
grid-area: 1 / 4;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
.tempo-header-inner {
|
||||
grid-template-columns: auto minmax(0, 1fr) auto auto auto;
|
||||
column-gap: 0.25rem;
|
||||
padding-inline: 1.25rem;
|
||||
}
|
||||
.tempo-logo-link {
|
||||
width: 100px;
|
||||
}
|
||||
.tempo-login-button > span {
|
||||
max-width: 3rem;
|
||||
}
|
||||
.tempo-primary-navigation,
|
||||
.tempo-header[data-expanded] .tempo-primary-navigation {
|
||||
display: block;
|
||||
grid-area: 1 / 2;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
justify-self: center;
|
||||
}
|
||||
.tempo-primary-navigation > ul {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0;
|
||||
}
|
||||
.tempo-nav-trigger {
|
||||
justify-content: center;
|
||||
padding-inline: 0.25rem;
|
||||
font-size: 1.125rem;
|
||||
letter-spacing: 0.025em;
|
||||
}
|
||||
.tempo-header-extras,
|
||||
.tempo-header[data-expanded] .tempo-header-extras {
|
||||
display: flex;
|
||||
grid-area: 1 / 3;
|
||||
padding: 0 0.5rem 0 0;
|
||||
border: 0;
|
||||
border-right: 1px solid #ffffff1a;
|
||||
}
|
||||
.tempo-header-utilities {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
.tempo-header-actions {
|
||||
grid-area: 1 / 4;
|
||||
}
|
||||
.tempo-interest-button {
|
||||
grid-area: 1 / 5;
|
||||
}
|
||||
.tempo-header-toggle {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1440px) {
|
||||
.tempo-header-inner {
|
||||
column-gap: 0.75rem;
|
||||
padding-inline: 2rem;
|
||||
}
|
||||
.tempo-nav-trigger {
|
||||
padding-inline: 0.375rem;
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
.tempo-logo-link {
|
||||
width: 132px;
|
||||
}
|
||||
.tempo-login-button > span {
|
||||
max-width: 5rem;
|
||||
}
|
||||
.tempo-header-extras {
|
||||
padding-right: 0.75rem;
|
||||
}
|
||||
.tempo-primary-navigation > ul {
|
||||
gap: 0.125rem;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1536px) {
|
||||
.tempo-header-inner {
|
||||
column-gap: 1rem;
|
||||
}
|
||||
.tempo-nav-trigger {
|
||||
padding-inline: 0.75rem;
|
||||
}
|
||||
.tempo-login-button > span {
|
||||
max-width: 6rem;
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ export function AppSidebar() {
|
||||
educationEnabled,
|
||||
aiEnabled,
|
||||
opportunityManagement,
|
||||
contentManagementHref,
|
||||
profileAccess,
|
||||
} = usePage<{
|
||||
auth: Auth;
|
||||
@@ -34,6 +35,7 @@ export function AppSidebar() {
|
||||
aiEnabled?: boolean;
|
||||
profileAccess?: ProfileAccess | null;
|
||||
opportunityManagement?: OpportunityManagementAccess | null;
|
||||
contentManagementHref?: string | null;
|
||||
}>().props;
|
||||
const groups = portalNavigation({
|
||||
authenticated: !!auth.user,
|
||||
@@ -48,6 +50,9 @@ export function AppSidebar() {
|
||||
href: group.children?.find((item) => item.href)?.href ?? '/',
|
||||
icon: LayoutGrid,
|
||||
}));
|
||||
if (contentManagementHref) {
|
||||
items.unshift({ title: 'Gestão de conteúdos e oportunidades', href: contentManagementHref, icon: LayoutGrid });
|
||||
}
|
||||
if (auth.canManagePortal && opportunityManagement) {
|
||||
items.push({
|
||||
title: opportunityManagement.manage.label,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Link, usePage } from '@inertiajs/react';
|
||||
import { searchTerritoryHref } from '@/lib/search-territory';
|
||||
export default function CatalogueNavigation({
|
||||
dimensionUrl,
|
||||
}: {
|
||||
@@ -21,7 +22,7 @@ export default function CatalogueNavigation({
|
||||
)
|
||||
return dimensionUrl(u.searchParams.get('dimension')!);
|
||||
if (context) u.searchParams.set('context', context);
|
||||
return u.pathname + u.search;
|
||||
return searchTerritoryHref(u.pathname + u.search, url);
|
||||
};
|
||||
const sections = {
|
||||
'Enquadramento e filtros': [
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { Link, usePage } from '@inertiajs/react';
|
||||
|
||||
export default function ContentManagementEntry() {
|
||||
const { contentManagementHref } = usePage<{ contentManagementHref?: string | null }>().props;
|
||||
return contentManagementHref ? <Link className="inline-block rounded-lg border px-4 py-3 font-semibold underline" href={contentManagementHref}>Gestão de conteúdos e oportunidades</Link> : null;
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { InvestmentContextAssessment } from './investment-context-assessment';
|
||||
import { Link } from '@inertiajs/react';
|
||||
import {
|
||||
contextApi,
|
||||
contextFieldNames,
|
||||
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';
|
||||
@@ -12,6 +15,7 @@ type Props = {
|
||||
enabled?: boolean;
|
||||
readOnly?: boolean;
|
||||
allowedFields?: string[];
|
||||
persistInheritance?: boolean;
|
||||
boundRevision?: number;
|
||||
onApply: (patch: Record<string, unknown>, replace: boolean) => void;
|
||||
onReview?: (revision: number, reviewed: boolean) => void;
|
||||
@@ -24,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,
|
||||
@@ -40,17 +48,16 @@ export function DossierPrefill(props: Props) {
|
||||
const [selected, setSelected] = useState<string[]>([]);
|
||||
const [reviewed, setReviewed] = useState(false);
|
||||
const [message, setMessage] = useState('');
|
||||
const filled = useRef('');
|
||||
useEffect(() => {
|
||||
if (!contextId) return;
|
||||
let stopped = false;
|
||||
setView(null);
|
||||
setError('');
|
||||
void contextApi<ContextView>(`/investimentos/${contextId}/contexto`)
|
||||
.then((v) => {
|
||||
if (!stopped) {
|
||||
setView(v);
|
||||
setReviewed(false);
|
||||
latest.current.onReview?.(v.context.revision, false);
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
@@ -61,44 +68,20 @@ export function DossierPrefill(props: Props) {
|
||||
};
|
||||
}, [contextId, retry]);
|
||||
useEffect(() => {
|
||||
if (!view || !enabled || readOnly || !view.can_write) return;
|
||||
const generation = `${contextId}:${view.context.revision}:${consumer}`;
|
||||
if (filled.current === generation) return;
|
||||
if (!view || view.case.id !== contextId || !enabled || readOnly || !view.can_write) return;
|
||||
const source = view.prefill[consumer] ?? {};
|
||||
const now = latest.current;
|
||||
const patch: Record<string, unknown> = {};
|
||||
const conflict = geo.some(
|
||||
(k) =>
|
||||
!empty(now.values[k]) &&
|
||||
String(now.values[k]) !== text(source[k]),
|
||||
);
|
||||
for (const [k, v] of Object.entries(source)) {
|
||||
if (
|
||||
(now.allowedFields && !now.allowedFields.includes(k)) ||
|
||||
(geo.includes(k) && conflict)
|
||||
)
|
||||
continue;
|
||||
if (k === 'parcels') {
|
||||
const old = now.values.parcels as
|
||||
| Record<string, unknown>[]
|
||||
| undefined;
|
||||
if (
|
||||
(!old?.length || geo.every((g) => empty(old[0]?.[g]))) &&
|
||||
Array.isArray(v) &&
|
||||
v[0] &&
|
||||
Object.keys(v[0]).length
|
||||
)
|
||||
patch[k] = v;
|
||||
} else if (empty(now.values[k]) && !empty(v)) patch[k] = v;
|
||||
}
|
||||
filled.current = generation;
|
||||
if (Object.keys(patch).length) {
|
||||
now.onApply(patch, false);
|
||||
const inherited = inheritCompatibleFields(`dossier:${contextId}:${consumer}`, now.values, source, now.values.context_reuse, now.allowedFields);
|
||||
if (Object.keys(inherited.patch).length || (now.persistInheritance !== false && now.values.context_reuse !== inherited.metadata)) {
|
||||
// The shared reconciliation has already selected safe fields; metadata travels with the same saved form.
|
||||
now.onApply({...inherited.patch, ...(now.persistInheritance === false ? {} : {context_reuse:inherited.metadata})}, true);
|
||||
setMessage(
|
||||
'Campos vazios preenchidos a partir do dossier. Reveja-os; os campos já preenchidos foram preservados.',
|
||||
'Dados compatíveis reutilizados do mesmo dossier. Os valores herdados acompanham o contexto; as suas alterações locais foram preservadas.',
|
||||
);
|
||||
}
|
||||
}, [view, enabled, readOnly, contextId, consumer]);
|
||||
setReviewed(true);
|
||||
now.onReview?.(view.context.revision, true);
|
||||
}, [view, enabled, readOnly, contextId, consumer, props.values]);
|
||||
if (!contextId) return null;
|
||||
if (error)
|
||||
return (
|
||||
@@ -126,7 +109,7 @@ export function DossierPrefill(props: Props) {
|
||||
),
|
||||
);
|
||||
const differences = Object.keys(source).filter(
|
||||
(k) => 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'
|
||||
@@ -139,38 +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>
|
||||
),
|
||||
)}
|
||||
@@ -235,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>
|
||||
))}
|
||||
@@ -253,8 +234,21 @@ export function DossierPrefill(props: Props) {
|
||||
onClick={() => {
|
||||
const patch: Record<string, unknown> = {};
|
||||
for (const k of selected)
|
||||
{
|
||||
patch[k] = source[k] ?? '';
|
||||
props.onApply(patch, true);
|
||||
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 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(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);
|
||||
props.onReview?.(view.context.revision, false);
|
||||
@@ -267,27 +261,7 @@ export function DossierPrefill(props: Props) {
|
||||
</button>
|
||||
</details>
|
||||
)}
|
||||
{!readOnly &&
|
||||
view.can_write &&
|
||||
props.onReview &&
|
||||
view.context.revision > 0 && (
|
||||
<label className="block">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={reviewed}
|
||||
onChange={(e) => {
|
||||
setReviewed(e.target.checked);
|
||||
props.onReview?.(
|
||||
view.context.revision,
|
||||
e.target.checked,
|
||||
);
|
||||
}}
|
||||
/>{' '}
|
||||
Conferi os dados e pressupostos deste formulário para o
|
||||
contexto v{view.context.revision}; os valores diferentes
|
||||
que conservei são declarações deste processo.
|
||||
</label>
|
||||
)}
|
||||
{!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.
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
export type EditorialArticle = {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string | null;
|
||||
author: string | null;
|
||||
body: string;
|
||||
html?: string | null;
|
||||
image_url: string | null;
|
||||
original_url: string;
|
||||
source_url?: string | null;
|
||||
published_at?: string | null;
|
||||
modified_at?: string | null;
|
||||
legacy_id?: number | null;
|
||||
image_source_url?: string | null;
|
||||
image_notice?: string | null;
|
||||
image_references?: string[];
|
||||
metadata_version?: number;
|
||||
private_preview?: boolean;
|
||||
};
|
||||
export default function EditorialReading({
|
||||
article,
|
||||
}: {
|
||||
article: EditorialArticle;
|
||||
}) {
|
||||
return (
|
||||
<div className="space-y-5">
|
||||
{article.private_preview && (
|
||||
<p
|
||||
role="status"
|
||||
className="rounded border border-amber-500 bg-amber-50 p-3 text-slate-900"
|
||||
>
|
||||
Prévia privada — não publica o artigo nem altera os direitos
|
||||
do original.
|
||||
</p>
|
||||
)}
|
||||
<h1 className="text-3xl font-bold">{article.title}</h1>
|
||||
{article.description && <p>{article.description}</p>}
|
||||
{article.author && <p>Autor / entidade: {article.author}</p>}
|
||||
{article.legacy_id && (
|
||||
<p>
|
||||
Arquivo do portal antigo · artigo {article.legacy_id}.
|
||||
Conteúdo editorial preservado; não corresponde por si a
|
||||
oportunidade concreta ou regra executável.
|
||||
</p>
|
||||
)}
|
||||
{(article.published_at || article.modified_at) && (
|
||||
<p>
|
||||
Origem: publicado em{' '}
|
||||
{article.published_at ?? 'data não indicada'} · modificado
|
||||
em {article.modified_at ?? 'data não indicada'}. A edição no
|
||||
portal conserva estas datas de origem.
|
||||
</p>
|
||||
)}
|
||||
{article.image_url && (
|
||||
<img
|
||||
className="max-h-96 w-full object-contain"
|
||||
src={article.image_url}
|
||||
alt={`Imagem do artigo ${article.title}`}
|
||||
/>
|
||||
)}
|
||||
{article.image_notice && (
|
||||
<p role="note" className="rounded border p-3">
|
||||
{article.image_notice}
|
||||
</p>
|
||||
)}
|
||||
{article.html ? (
|
||||
<article
|
||||
className="prose dark:prose-invert max-w-none overflow-x-auto"
|
||||
dangerouslySetInnerHTML={{ __html: article.html }}
|
||||
/>
|
||||
) : (
|
||||
<article className="space-y-5">
|
||||
{article.body.split(/\n\s*\n/).map((p, i) => (
|
||||
<p className="whitespace-pre-wrap" key={i}>
|
||||
{p}
|
||||
</p>
|
||||
))}
|
||||
</article>
|
||||
)}
|
||||
{!!article.image_references?.length && (
|
||||
<section aria-label="Imagens referenciadas no corpo original">
|
||||
<h2>Imagens referenciadas no corpo original</h2>
|
||||
<p>
|
||||
Referências preservadas; não confirmam um ficheiro
|
||||
disponível na biblioteca.
|
||||
</p>
|
||||
<ul>
|
||||
{article.image_references.map((url, i) => (
|
||||
<li key={url}>
|
||||
<a
|
||||
className="underline"
|
||||
href={url}
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Referência da imagem {i + 1}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
)}
|
||||
<nav aria-label="Origem do artigo" className="flex flex-wrap gap-4">
|
||||
{article.source_url && (
|
||||
<a
|
||||
className="underline"
|
||||
href={article.source_url}
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Consultar página de origem
|
||||
</a>
|
||||
)}
|
||||
{article.image_source_url && (
|
||||
<a
|
||||
className="underline"
|
||||
href={article.image_source_url}
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Referência da imagem de origem
|
||||
</a>
|
||||
)}
|
||||
<a href={article.original_url} className="underline">
|
||||
Consultar documento original
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -19,6 +19,8 @@ export type InvestmentMapFeature = {
|
||||
};
|
||||
};
|
||||
export type InvestmentMapArea = {
|
||||
selected_territory?: boolean;
|
||||
location_type?: string;
|
||||
purpose?: string;
|
||||
geometry: GeoJsonObject;
|
||||
title: string;
|
||||
@@ -74,6 +76,13 @@ export default function InteractiveInvestmentMap({
|
||||
.addTo(instance);
|
||||
L.control.scale({ imperial: false }).addTo(instance);
|
||||
const bounds: [number, number][] = [];
|
||||
const hasSelectedBoundary = areas.some((area) => area.selected_territory);
|
||||
const administrativeLevel: Record<string, number> = {
|
||||
province: 1, district: 2, admin_post: 3, locality: 4,
|
||||
};
|
||||
const closestContextLevel = contextOnly
|
||||
? Math.max(0, ...areas.map((area) => administrativeLevel[area.location_type ?? ''] ?? 0))
|
||||
: 0;
|
||||
for (const area of areas) {
|
||||
const label = document.createElement('p');
|
||||
label.textContent = `${area.title} · ${area.purpose === 'administrative_boundary' ? 'Limite administrativo da edição indicada; não representa um local de investimento' : 'Polígono cadastrado, limites oficiais por verificar'} · Fonte: ${area.source_reference ?? 'Não indicada'} · Data: ${area.source_date ?? 'Não documentada'}`;
|
||||
@@ -92,7 +101,10 @@ export default function InteractiveInvestmentMap({
|
||||
.bindPopup(label)
|
||||
.addTo(instance);
|
||||
const extent = layer.getBounds();
|
||||
if (extent.isValid())
|
||||
const focusBoundary = hasSelectedBoundary
|
||||
? area.selected_territory
|
||||
: !closestContextLevel || administrativeLevel[area.location_type ?? ''] === closestContextLevel;
|
||||
if (extent.isValid() && focusBoundary)
|
||||
bounds.push(
|
||||
[extent.getSouth(), extent.getWest()],
|
||||
[extent.getNorth(), extent.getEast()],
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
import {
|
||||
PreparationSummary,
|
||||
type Preparation,
|
||||
} from '@/components/private-investment-preparation';
|
||||
import { Link } from '@inertiajs/react';
|
||||
|
||||
type Snapshot = {
|
||||
preparation?: Preparation | null;
|
||||
context_revision: number;
|
||||
path?: {
|
||||
definition_version: number;
|
||||
steps: Array<{
|
||||
code: string;
|
||||
title: string;
|
||||
applicability: string;
|
||||
reason: string;
|
||||
state: string;
|
||||
context_review_required?: boolean;
|
||||
}>;
|
||||
};
|
||||
zone?: {
|
||||
title: string;
|
||||
href: string;
|
||||
version: number | string;
|
||||
fields?: Record<string, string>;
|
||||
analysis?: {
|
||||
conditions?: Array<{
|
||||
id: string;
|
||||
label: string;
|
||||
knowledge: string;
|
||||
source_reference?: string;
|
||||
valid_from?: string;
|
||||
valid_until?: string;
|
||||
}>;
|
||||
source?: {
|
||||
title: string;
|
||||
url: string;
|
||||
diploma?: string;
|
||||
article?: string;
|
||||
valid_from?: string;
|
||||
valid_until?: string;
|
||||
} | null;
|
||||
};
|
||||
} | null;
|
||||
fields?: Record<string, string>;
|
||||
};
|
||||
export function InvestmentContextAssessment({
|
||||
raw,
|
||||
contextId,
|
||||
}: {
|
||||
raw: unknown;
|
||||
contextId?: string | null;
|
||||
}) {
|
||||
let snapshot: Snapshot | null = null;
|
||||
try {
|
||||
const value = JSON.parse(String(raw ?? ''));
|
||||
if (value.version === 1) snapshot = value;
|
||||
} catch {
|
||||
/* Missing context remains explicit. */
|
||||
}
|
||||
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',
|
||||
zfi: 'ZFI',
|
||||
industrial_park: 'Parque industrial',
|
||||
};
|
||||
return (
|
||||
<section
|
||||
className="space-y-2 rounded border border-amber-400 p-3"
|
||||
aria-label="Aplicabilidade ao investimento"
|
||||
>
|
||||
<h3 className="font-semibold">
|
||||
Condições do investimento
|
||||
</h3>
|
||||
{(fields.investor_origin || fields.tax_residency_country) && <p>
|
||||
Origem do investidor:{' '}
|
||||
{{
|
||||
national: 'nacional',
|
||||
foreign: 'estrangeiro',
|
||||
mixed: 'misto',
|
||||
}[fields.investor_origin] ?? 'informação em falta'}
|
||||
. Residência fiscal declarada:{' '}
|
||||
{fields.tax_residency_country || 'Por preencher'}.
|
||||
</p>}
|
||||
{zone ? (
|
||||
<>
|
||||
<p>
|
||||
<strong>
|
||||
Potencialmente aplicável — análise específica de{' '}
|
||||
{names[regime ?? ''] ?? 'zona seleccionada'}.
|
||||
</strong>{' '}
|
||||
<Link className="underline" href={zone.href}>
|
||||
{zone.title} · consultar condições
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
<p>
|
||||
A selecção identifica a referência consultada; não
|
||||
confirma o reconhecimento da entidade, actividade
|
||||
autorizada, licença ou acesso a benefícios. Consulte as
|
||||
condições documentadas na ficha e reúna a autorização ou
|
||||
evidência correspondente no dossier.
|
||||
</p>
|
||||
{zone.analysis?.source && (
|
||||
<p>
|
||||
Fundamento cadastrado:{' '}
|
||||
<a
|
||||
className="underline"
|
||||
href={zone.analysis.source.url}
|
||||
>
|
||||
{zone.analysis.source.diploma ||
|
||||
zone.analysis.source.title}{' '}
|
||||
{zone.analysis.source.article}
|
||||
</a>
|
||||
. Período cadastrado:{' '}
|
||||
{zone.analysis.source.valid_from ||
|
||||
'início por indicar'}{' '}
|
||||
a{' '}
|
||||
{zone.analysis.source.valid_until ||
|
||||
'sem termo indicado'}
|
||||
. A cobertura do investimento exige correspondência
|
||||
com estas condições.
|
||||
</p>
|
||||
)}
|
||||
{zone.analysis?.conditions?.length ? (
|
||||
<ul className="list-disc pl-5">
|
||||
{zone.analysis.conditions.map((condition) => (
|
||||
<li key={condition.id}>
|
||||
<strong>{condition.label}</strong>:{' '}
|
||||
{condition.knowledge === 'documented'
|
||||
? 'condição documentada; cumprimento pelo investimento por confirmar'
|
||||
: condition.knowledge === 'disputed'
|
||||
? 'condição com informação divergente'
|
||||
: 'informação em falta'}
|
||||
.{' '}
|
||||
{condition.source_reference && (
|
||||
<>
|
||||
Fundamento:{' '}
|
||||
{condition.source_reference}.
|
||||
</>
|
||||
)}{' '}
|
||||
{condition.valid_from && (
|
||||
<>Desde {condition.valid_from}.</>
|
||||
)}{' '}
|
||||
{condition.valid_until && (
|
||||
<>Até {condition.valid_until}.</>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<p>
|
||||
Condições específicas: informação estruturada em
|
||||
falta nesta referência. A ficha e os documentos
|
||||
continuam consultáveis; não se presume cumprimento.
|
||||
</p>
|
||||
)}
|
||||
<p role="status">
|
||||
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>
|
||||
</>
|
||||
) : null}
|
||||
{!!steps.length && (
|
||||
<details>
|
||||
<summary>
|
||||
Etapas a preparar
|
||||
</summary>
|
||||
<ul className="list-disc pl-5">
|
||||
{steps.map((step) => (
|
||||
<li key={step.code}>
|
||||
{step.title}:{' '}
|
||||
{step.context_review_required
|
||||
? 'informação alterada; rever fundamento'
|
||||
: step.applicability ===
|
||||
'not_needed_for_preparation'
|
||||
? 'não aplicável à preparação declarada'
|
||||
: step.applicability === 'undetermined'
|
||||
? 'informação em falta'
|
||||
: ['completed', 'approved'].includes(
|
||||
step.state,
|
||||
)
|
||||
? 'preparação confirmada no percurso; não é decisão fiscal'
|
||||
: 'potencialmente aplicável'}
|
||||
. {step.reason}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</details>
|
||||
)}
|
||||
{snapshot.preparation?.enabled && (
|
||||
<PreparationSummary
|
||||
data={snapshot.preparation}
|
||||
caseId={contextId ?? undefined}
|
||||
/>
|
||||
)}
|
||||
{contextId && (
|
||||
<Link
|
||||
className="underline"
|
||||
href={`/investimentos/${contextId}/contexto/editar`}
|
||||
>
|
||||
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>;
|
||||
}
|
||||
@@ -48,6 +48,16 @@ const labels: Record<string, string> = {
|
||||
authority_id: 'Entidade',
|
||||
publication_state: 'Critério herdado',
|
||||
};
|
||||
const specialisedEmpty: Record<string, string> = {
|
||||
corridor_id: 'Não existem corredores públicos cadastrados para esta localização.',
|
||||
cluster_id: 'Não existem clusters públicos cadastrados para esta localização.',
|
||||
zee_id: 'Não existem ZEE públicas cadastradas para esta localização.',
|
||||
zfi_id: 'Não existem ZFI públicas cadastradas para esta localização.',
|
||||
park_id: 'Não existem parques industriais públicos cadastrados para esta localização.',
|
||||
project_id: 'Não existem projectos âncora públicos cadastrados para esta localização.',
|
||||
subsector_id: 'Não existem actividades disponíveis no catálogo público.',
|
||||
chain_id: 'Não existem cadeias de valor públicas cadastradas para esta localização.',
|
||||
};
|
||||
export default function OpportunitySearchFilters({
|
||||
data,
|
||||
references,
|
||||
@@ -75,6 +85,9 @@ export default function OpportunitySearchFilters({
|
||||
<Field key={key} label={labels[key]}>
|
||||
<select
|
||||
className={input}
|
||||
aria-label={labels[key]}
|
||||
aria-describedby={specialisedEmpty[key] ? `filter-help-${key}` : undefined}
|
||||
disabled={Boolean(specialisedEmpty[key]) && choices.length === 0 && !data[key]}
|
||||
value={String(data[key] ?? '')}
|
||||
onChange={(e) =>
|
||||
change({
|
||||
@@ -86,12 +99,23 @@ export default function OpportunitySearchFilters({
|
||||
}
|
||||
>
|
||||
<option value="">Todos</option>
|
||||
{data[key] && !choices.some((o) => o.value === String(data[key])) && (
|
||||
<option value={String(data[key])}>Selecção indisponível neste contexto — remova o filtro</option>
|
||||
)}
|
||||
{choices.map((o) => (
|
||||
<option key={o.value} value={o.value}>
|
||||
{o.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{specialisedEmpty[key] && (
|
||||
<p id={`filter-help-${key}`} className="text-sm text-slate-600">
|
||||
{choices.length === 0
|
||||
? specialisedEmpty[key]
|
||||
: `${choices.length} opções públicas disponíveis, mesmo sem oportunidades associadas.`}
|
||||
{key === 'subsector_id' && choices.length > 0 && ' Classificação de actividades; não confirma a existência de investimentos nesta localização.'}
|
||||
</p>
|
||||
)}
|
||||
{hint && <p className="text-sm text-slate-600">{hint}</p>}
|
||||
</Field>
|
||||
);
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import PortalProductIdentity from '@/components/portal-product-identity';
|
||||
import { searchTerritoryHref } from '@/lib/search-territory';
|
||||
import { Link, router, usePage } from '@inertiajs/react';
|
||||
import {
|
||||
ChevronDown,
|
||||
ArrowUpRight,
|
||||
LayoutDashboard,
|
||||
LogOut,
|
||||
Menu,
|
||||
UserRound,
|
||||
X,
|
||||
} from 'lucide-react';
|
||||
import { useEffect, useId, useRef, useState } from 'react';
|
||||
import { Fragment, useEffect, useId, useRef, useState } from 'react';
|
||||
import { useIsMobile } from '@/hooks/use-mobile';
|
||||
import { useNavigationContext } from '@/components/navigation-context';
|
||||
import { TempoSocialLinks, useTempoUi } from '@/components/tempo-brand';
|
||||
import { tempoSocialLinks, useTempoUi } from '@/components/tempo-brand';
|
||||
import {
|
||||
TempoLanguageSelector,
|
||||
TempoWeather,
|
||||
@@ -45,6 +48,7 @@ export function PortalGroupedMenu() {
|
||||
existingBusinessEnabled?: boolean;
|
||||
businessReviewUrl?: string | null;
|
||||
educationEnabled?: boolean;
|
||||
procurementEnabled?: boolean;
|
||||
aiEnabled?: boolean;
|
||||
legacyContentEnabled?: boolean;
|
||||
profileAccess?: ProfileAccess | null;
|
||||
@@ -52,6 +56,7 @@ export function PortalGroupedMenu() {
|
||||
scopedAdministration?: {label:string;href:string}[];
|
||||
}>();
|
||||
const { t, language, requestCount } = useTempoUi();
|
||||
const mobile = useIsMobile();
|
||||
const navigation = useNavigationContext();
|
||||
const groups = portalNavigation({
|
||||
context: navigation.view
|
||||
@@ -62,6 +67,7 @@ export function PortalGroupedMenu() {
|
||||
businesses: props.existingBusinessEnabled,
|
||||
businessReviewUrl: props.businessReviewUrl,
|
||||
education: props.educationEnabled,
|
||||
procurement: props.procurementEnabled,
|
||||
ai: props.aiEnabled,
|
||||
legacyContent: props.legacyContentEnabled,
|
||||
opportunityManagement: props.opportunityManagement,
|
||||
@@ -72,24 +78,34 @@ export function PortalGroupedMenu() {
|
||||
const currentItem = currentNavigationItem(groups, url);
|
||||
const [loggingOut, setLoggingOut] = useState(false);
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const institutionalId = useId();
|
||||
const navigationId = useId();
|
||||
const toolsId = useId();
|
||||
const interestId = useId();
|
||||
const toggle = useRef<HTMLButtonElement>(null);
|
||||
useEffect(() => router.on('navigate', () => setExpanded(false)), []);
|
||||
const items = (children: PortalNavItem[]) =>
|
||||
children.map((item) =>
|
||||
item.children ? (
|
||||
<DropdownMenuSub key={item.id}>
|
||||
<DropdownMenuSubTrigger className="tempo-menu-item font-semibold">
|
||||
{t(item.label)}
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent
|
||||
collisionPadding={12}
|
||||
className="tempo-dropdown max-h-[min(70vh,34rem)] w-72 max-w-[calc(100vw-1.5rem)] overflow-y-auto p-2"
|
||||
>
|
||||
mobile ? (
|
||||
<Fragment key={item.id}>
|
||||
<DropdownMenuLabel className="mt-2 border-t px-3 pt-3 text-xs text-slate-500">
|
||||
{t(item.label)}
|
||||
</DropdownMenuLabel>
|
||||
{items(item.children)}
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
</Fragment>
|
||||
) : (
|
||||
<DropdownMenuSub key={item.id}>
|
||||
<DropdownMenuSubTrigger className="tempo-menu-item font-semibold">
|
||||
{t(item.label)}
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent
|
||||
collisionPadding={12}
|
||||
className="tempo-dropdown tempo-submenu max-h-[min(70vh,34rem)] w-72 overflow-y-auto p-2"
|
||||
>
|
||||
{items(item.children)}
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
)
|
||||
) : (
|
||||
<DropdownMenuItem
|
||||
key={item.id}
|
||||
@@ -97,7 +113,7 @@ export function PortalGroupedMenu() {
|
||||
className="tempo-menu-item"
|
||||
>
|
||||
<Link
|
||||
href={item.href!}
|
||||
href={searchTerritoryHref(item.href!, url)}
|
||||
aria-current={
|
||||
currentItem === item.id ? 'page' : undefined
|
||||
}
|
||||
@@ -111,6 +127,8 @@ export function PortalGroupedMenu() {
|
||||
(group) =>
|
||||
!['home', 'discovery', 'realization', 'support'].includes(group.id),
|
||||
);
|
||||
const panelLabel = `${t('O meu painel')} — ${requestCount} ${t(requestCount === 1 ? 'pedido recebido' : 'pedidos recebidos')}`;
|
||||
const institutionalCurrent = url.split('?')[0] === '/contacto';
|
||||
return (
|
||||
<header
|
||||
className="tempo-header"
|
||||
@@ -120,245 +138,303 @@ export function PortalGroupedMenu() {
|
||||
if (
|
||||
event.key === 'Escape' &&
|
||||
expanded &&
|
||||
!event.defaultPrevented
|
||||
!event.defaultPrevented &&
|
||||
!(
|
||||
event.target instanceof Element &&
|
||||
event.target.closest('[role="menu"]')
|
||||
)
|
||||
) {
|
||||
setExpanded(false);
|
||||
toggle.current?.focus();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="tempo-header-layout">
|
||||
<div className="tempo-header-inner">
|
||||
<Link
|
||||
href="/"
|
||||
aria-label="InvesteMoz — início"
|
||||
className="tempo-logo-link"
|
||||
>
|
||||
<PortalProductIdentity />
|
||||
</Link>
|
||||
<nav
|
||||
id={institutionalId}
|
||||
aria-label="Navegação institucional"
|
||||
className="tempo-primary-navigation"
|
||||
>
|
||||
<ul>
|
||||
<li>
|
||||
<Link
|
||||
href="/"
|
||||
className="tempo-nav-trigger"
|
||||
aria-current={
|
||||
url.split('?')[0] === '/'
|
||||
? 'page'
|
||||
: undefined
|
||||
}
|
||||
data-current-group={
|
||||
url.split('?')[0] === '/' || undefined
|
||||
}
|
||||
>
|
||||
{t('Início')}
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://revista.tempo.co.mz/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="tempo-nav-trigger"
|
||||
aria-label={`${t('Revista Tempo')} — ${t('Abre num novo separador')}`}
|
||||
>
|
||||
{t('Revista Tempo')}
|
||||
<ArrowUpRight
|
||||
size={14}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
{[
|
||||
['/sobre', 'Sobre nós'],
|
||||
['/contacto', 'Contacto'],
|
||||
].map(([href, label]) => (
|
||||
<li key={href}>
|
||||
<Link
|
||||
href={href}
|
||||
className="tempo-nav-trigger"
|
||||
aria-current={
|
||||
url.split('?')[0] === href
|
||||
? 'page'
|
||||
: undefined
|
||||
}
|
||||
data-current-group={
|
||||
url.split('?')[0] === href ||
|
||||
undefined
|
||||
}
|
||||
>
|
||||
{t(label)}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
<div id={toolsId} className="tempo-header-extras">
|
||||
<div className="tempo-header-utilities">
|
||||
<div className="flex items-center gap-1">
|
||||
<TempoWeather />
|
||||
<TempoLanguageSelector />
|
||||
</div>
|
||||
<TempoSocialLinks />
|
||||
</div>
|
||||
</div>
|
||||
<div className="tempo-header-actions">
|
||||
{props.auth?.user ? (
|
||||
<DropdownMenu modal={false}>
|
||||
<DropdownMenuTrigger className="tempo-login-button">
|
||||
<UserRound size={16} aria-hidden="true" />
|
||||
<span className="max-w-24 truncate">
|
||||
{props.auth.user.name ||
|
||||
t('A minha área')}
|
||||
</span>
|
||||
<ChevronDown size={12} aria-hidden="true" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="end"
|
||||
sideOffset={12}
|
||||
collisionPadding={12}
|
||||
className="tempo-dropdown max-h-[75vh] w-72 max-w-[calc(100vw-1.5rem)] overflow-y-auto p-2"
|
||||
>
|
||||
<DropdownMenuLabel className="truncate text-xs text-slate-500">
|
||||
{props.auth.user.email}
|
||||
</DropdownMenuLabel>
|
||||
{items(accountGroups)}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
asChild
|
||||
className="tempo-menu-item text-red-700"
|
||||
>
|
||||
<Link
|
||||
href={logout()}
|
||||
as="button"
|
||||
type="button"
|
||||
disabled={loggingOut}
|
||||
data-test="portal-logout-button"
|
||||
onClick={() => router.flushAll()}
|
||||
onStart={() => setLoggingOut(true)}
|
||||
onFinish={() =>
|
||||
setLoggingOut(false)
|
||||
<div className="tempo-header-inner">
|
||||
<Link
|
||||
href="/"
|
||||
aria-label="Tempo — início"
|
||||
className="tempo-logo-link"
|
||||
>
|
||||
<img
|
||||
src="/images/logo/logo-vermelho.png"
|
||||
alt="Tempo"
|
||||
width="1787"
|
||||
height="564"
|
||||
className="tempo-header-logo"
|
||||
/>
|
||||
</Link>
|
||||
<nav
|
||||
id={navigationId}
|
||||
aria-label="Áreas do portal"
|
||||
className="tempo-primary-navigation"
|
||||
>
|
||||
<ul>
|
||||
<li>
|
||||
<Link
|
||||
href="/"
|
||||
className="tempo-nav-trigger"
|
||||
aria-current={
|
||||
url.split('?')[0] === '/'
|
||||
? 'page'
|
||||
: undefined
|
||||
}
|
||||
data-current-group={
|
||||
url.split('?')[0] === '/' || undefined
|
||||
}
|
||||
>
|
||||
{t('Início')}
|
||||
</Link>
|
||||
</li>
|
||||
{groups
|
||||
.filter((group) =>
|
||||
[
|
||||
'discovery',
|
||||
'realization',
|
||||
'support',
|
||||
].includes(group.id),
|
||||
)
|
||||
.map((group) => (
|
||||
<li key={group.id}>
|
||||
<DropdownMenu modal={false}>
|
||||
<DropdownMenuTrigger
|
||||
className="tempo-nav-trigger"
|
||||
data-current-group={
|
||||
current === group.id ||
|
||||
undefined
|
||||
}
|
||||
>
|
||||
<LogOut
|
||||
size={16}
|
||||
{t(group.label)}
|
||||
<ChevronDown
|
||||
size={14}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{loggingOut
|
||||
? 'A terminar sessão…'
|
||||
: t('Terminar sessão')}
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
) : (
|
||||
<Link href="/login" className="tempo-login-button">
|
||||
<UserRound size={15} aria-hidden="true" />
|
||||
{t('Entrar')}
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
ref={toggle}
|
||||
type="button"
|
||||
className="tempo-header-toggle"
|
||||
aria-label={
|
||||
expanded
|
||||
? 'Fechar menu e ferramentas'
|
||||
: 'Abrir menu e ferramentas'
|
||||
}
|
||||
aria-expanded={expanded}
|
||||
aria-controls={`${institutionalId} ${toolsId}`}
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
>
|
||||
{expanded ? (
|
||||
<X size={22} aria-hidden="true" />
|
||||
) : (
|
||||
<Menu size={22} aria-hidden="true" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<div className="tempo-section-bar">
|
||||
<div className="tempo-section-inner">
|
||||
<Link
|
||||
href="/investidor/manifestacoes"
|
||||
className="tempo-panel-summary"
|
||||
>
|
||||
<span>{t('O meu painel')}</span>
|
||||
<span
|
||||
className="tempo-panel-count"
|
||||
aria-live="polite"
|
||||
aria-atomic="true"
|
||||
{current === group.id && (
|
||||
<span className="sr-only">
|
||||
Grupo actual
|
||||
</span>
|
||||
)}
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="start"
|
||||
sideOffset={12}
|
||||
collisionPadding={12}
|
||||
className="tempo-dropdown max-h-[min(75vh,36rem)] w-72 max-w-[calc(100vw-1.5rem)] overflow-y-auto p-2"
|
||||
>
|
||||
{items(group.children ?? [])}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</li>
|
||||
))}
|
||||
<li>
|
||||
<Link
|
||||
href="/sobre"
|
||||
className="tempo-nav-trigger"
|
||||
aria-current={
|
||||
url.split('?')[0] === '/sobre'
|
||||
? 'page'
|
||||
: undefined
|
||||
}
|
||||
data-current-group={
|
||||
url.split('?')[0] === '/sobre' || undefined
|
||||
}
|
||||
>
|
||||
{requestCount}{' '}
|
||||
{t(
|
||||
requestCount === 1
|
||||
? 'pedido recebido'
|
||||
: 'pedidos recebidos',
|
||||
)}
|
||||
</span>
|
||||
</Link>
|
||||
<nav
|
||||
aria-label="Áreas do portal"
|
||||
className="tempo-section-navigation"
|
||||
>
|
||||
<ul>
|
||||
{groups
|
||||
.filter((group) =>
|
||||
[
|
||||
'discovery',
|
||||
'realization',
|
||||
'support',
|
||||
].includes(group.id),
|
||||
)
|
||||
.map((group) => (
|
||||
<li key={group.id}>
|
||||
<DropdownMenu modal={false}>
|
||||
<DropdownMenuTrigger
|
||||
className="tempo-nav-trigger"
|
||||
data-current-group={
|
||||
current === group.id ||
|
||||
undefined
|
||||
{t('Sobre nós')}
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<DropdownMenu modal={false}>
|
||||
<DropdownMenuTrigger
|
||||
className="tempo-nav-trigger"
|
||||
data-current-group={
|
||||
institutionalCurrent || undefined
|
||||
}
|
||||
>
|
||||
Tempo
|
||||
<ChevronDown size={14} aria-hidden="true" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="start"
|
||||
sideOffset={12}
|
||||
collisionPadding={12}
|
||||
className="tempo-dropdown max-h-[75vh] w-64 max-w-[calc(100vw-1.5rem)] overflow-y-auto p-2"
|
||||
>
|
||||
<DropdownMenuItem
|
||||
asChild
|
||||
className="tempo-menu-item"
|
||||
>
|
||||
<a
|
||||
href="https://revista.tempo.co.mz/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label={`${t('Revista Tempo')} — ${t('Abre num novo separador')}`}
|
||||
>
|
||||
{t('Revista Tempo')}
|
||||
<ArrowUpRight
|
||||
size={14}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</a>
|
||||
</DropdownMenuItem>
|
||||
{[['/contacto', 'Contacto']].map(
|
||||
([href, label]) => (
|
||||
<DropdownMenuItem
|
||||
key={href}
|
||||
asChild
|
||||
className="tempo-menu-item"
|
||||
>
|
||||
<Link
|
||||
href={href}
|
||||
aria-current={
|
||||
url.split('?')[0] ===
|
||||
href
|
||||
? 'page'
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{t(group.label)}
|
||||
<ChevronDown
|
||||
size={14}
|
||||
{t(label)}
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
),
|
||||
)}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuLabel className="text-xs text-slate-500">
|
||||
Revista Tempo
|
||||
</DropdownMenuLabel>
|
||||
{tempoSocialLinks.map(
|
||||
({ name, href, icon: Icon }) => (
|
||||
<DropdownMenuItem
|
||||
key={name}
|
||||
asChild
|
||||
className="tempo-menu-item"
|
||||
>
|
||||
<a
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label={`Revista Tempo — ${name}`}
|
||||
>
|
||||
<Icon
|
||||
size={16}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{current === group.id && (
|
||||
<span className="sr-only">
|
||||
Grupo actual
|
||||
</span>
|
||||
)}
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="start"
|
||||
sideOffset={8}
|
||||
collisionPadding={12}
|
||||
className="tempo-dropdown max-h-[min(75vh,36rem)] w-72 max-w-[calc(100vw-1.5rem)] overflow-y-auto p-2"
|
||||
>
|
||||
{items(
|
||||
group.children ?? [],
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
<Link
|
||||
href="/manifestar-interesse"
|
||||
className="tempo-interest-button"
|
||||
>
|
||||
{t('Manifestar interesse')}
|
||||
</Link>
|
||||
{name}
|
||||
<ArrowUpRight
|
||||
size={12}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</a>
|
||||
</DropdownMenuItem>
|
||||
),
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div id={toolsId} className="tempo-header-extras">
|
||||
<div className="tempo-header-utilities">
|
||||
<div className="flex items-center gap-1">
|
||||
<TempoWeather />
|
||||
<TempoLanguageSelector />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="tempo-header-actions">
|
||||
<Link
|
||||
href="/investidor/manifestacoes"
|
||||
className="tempo-panel-summary"
|
||||
aria-label={panelLabel}
|
||||
title={panelLabel}
|
||||
>
|
||||
<LayoutDashboard size={19} aria-hidden="true" />
|
||||
<span className="tempo-panel-count" aria-hidden="true">
|
||||
{requestCount}
|
||||
</span>
|
||||
</Link>
|
||||
<span
|
||||
className="sr-only"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
aria-atomic="true"
|
||||
>
|
||||
{panelLabel}
|
||||
</span>
|
||||
{props.auth?.user ? (
|
||||
<DropdownMenu modal={false}>
|
||||
<DropdownMenuTrigger className="tempo-login-button">
|
||||
<UserRound size={16} aria-hidden="true" />
|
||||
<span className="max-w-24 truncate">
|
||||
{props.auth.user.name || t('A minha área')}
|
||||
</span>
|
||||
<ChevronDown size={12} aria-hidden="true" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="end"
|
||||
sideOffset={12}
|
||||
collisionPadding={12}
|
||||
className="tempo-dropdown max-h-[75vh] w-72 max-w-[calc(100vw-1.5rem)] overflow-y-auto p-2"
|
||||
>
|
||||
<DropdownMenuLabel className="truncate text-xs text-slate-500">
|
||||
{props.auth.user.email}
|
||||
</DropdownMenuLabel>
|
||||
{items(accountGroups)}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
asChild
|
||||
className="tempo-menu-item text-red-700"
|
||||
>
|
||||
<Link
|
||||
href={logout()}
|
||||
as="button"
|
||||
type="button"
|
||||
disabled={loggingOut}
|
||||
data-test="portal-logout-button"
|
||||
onClick={() => router.flushAll()}
|
||||
onStart={() => setLoggingOut(true)}
|
||||
onFinish={() => setLoggingOut(false)}
|
||||
>
|
||||
<LogOut size={16} aria-hidden="true" />
|
||||
{loggingOut
|
||||
? 'A terminar sessão…'
|
||||
: t('Terminar sessão')}
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
) : (
|
||||
<Link href="/login" className="tempo-login-button">
|
||||
<UserRound size={15} aria-hidden="true" />
|
||||
{t('Entrar')}
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
<Link
|
||||
id={interestId}
|
||||
href="/manifestar-interesse"
|
||||
className="tempo-interest-button"
|
||||
>
|
||||
{t('Manifestar interesse')}
|
||||
<ArrowUpRight size={15} aria-hidden="true" />
|
||||
</Link>
|
||||
<button
|
||||
ref={toggle}
|
||||
type="button"
|
||||
className="tempo-header-toggle"
|
||||
aria-label={
|
||||
expanded
|
||||
? 'Fechar menu e ferramentas'
|
||||
: 'Abrir menu e ferramentas'
|
||||
}
|
||||
aria-expanded={expanded}
|
||||
aria-controls={`${navigationId} ${toolsId} ${interestId}`}
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
>
|
||||
{expanded ? (
|
||||
<X size={22} aria-hidden="true" />
|
||||
) : (
|
||||
<Menu size={22} aria-hidden="true" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,457 @@
|
||||
import { Link } from '@inertiajs/react';
|
||||
import { card, input } from '@/pages/investment/shared';
|
||||
|
||||
export type PreparationDocument = {
|
||||
document_id: string;
|
||||
file_name: string;
|
||||
status: string;
|
||||
malware_scan_status: string;
|
||||
};
|
||||
export type Preparation = {
|
||||
version: string;
|
||||
enabled: boolean;
|
||||
mode: string;
|
||||
period_covered: boolean;
|
||||
scope_covered: boolean;
|
||||
reason: string[];
|
||||
missing_fields: string[];
|
||||
missing_labels?: string[];
|
||||
authority: string;
|
||||
next: string;
|
||||
calculation_notice: string;
|
||||
notice: string;
|
||||
source: { url: string; sha256: string };
|
||||
authority_source: { reference: string; url: string };
|
||||
questions: Array<{
|
||||
key: string;
|
||||
label: string;
|
||||
type?: string;
|
||||
options?: Record<string, string>;
|
||||
when?: { field: string; equals: string };
|
||||
source: string;
|
||||
help?: string;
|
||||
}>;
|
||||
requirements: Array<{
|
||||
key: string;
|
||||
label: string;
|
||||
state: string;
|
||||
required: boolean;
|
||||
document_id: string | null;
|
||||
document: PreparationDocument | null;
|
||||
stage: string;
|
||||
reason: string;
|
||||
source: { reference: string; url: string; period: string };
|
||||
}>;
|
||||
};
|
||||
const states: Record<string, string> = {
|
||||
not_applicable: 'Não aplicável neste contexto',
|
||||
indeterminate: 'Informação em falta para decidir',
|
||||
missing: 'Documento por associar',
|
||||
associated: 'Documento associado — conteúdo por conferir',
|
||||
};
|
||||
export const preparationMode = (mode: string) =>
|
||||
({
|
||||
authorization: 'Pedido de autorização',
|
||||
registration: 'Pedido de mero registo',
|
||||
indeterminate: 'Enquadramento por determinar',
|
||||
})[mode] ?? mode;
|
||||
|
||||
export function PreparationSummary({
|
||||
data,
|
||||
caseId,
|
||||
}: {
|
||||
data: Preparation | null | undefined;
|
||||
caseId?: string;
|
||||
}) {
|
||||
if (!data) return null;
|
||||
return (
|
||||
<section
|
||||
id="private-investment-preparation"
|
||||
className={card + ' space-y-3'}
|
||||
aria-label="Preparação adaptada do investimento privado"
|
||||
>
|
||||
<h2 className="text-xl font-bold">
|
||||
Preparar o pedido de investimento privado
|
||||
</h2>
|
||||
{!data.enabled ? (
|
||||
<p>
|
||||
Use os dados deste dossier para identificar o pedido e os
|
||||
documentos aplicáveis. A preparação é opcional e não atribui
|
||||
benefícios.
|
||||
</p>
|
||||
) : (
|
||||
<>
|
||||
<p>
|
||||
<strong>{preparationMode(data.mode)}</strong> · recorte
|
||||
de 2026 ·{' '}
|
||||
{data.version.replace(
|
||||
'private-investment-preparation-',
|
||||
'',
|
||||
)}
|
||||
</p>
|
||||
<p>{data.next}</p>
|
||||
{data.missing_fields.length > 0 && (
|
||||
<p>
|
||||
Informação em falta:{' '}
|
||||
{(
|
||||
data.missing_labels ??
|
||||
data.missing_fields.map(
|
||||
(key) =>
|
||||
data.questions?.find(
|
||||
(q) => q.key === key,
|
||||
)?.label ??
|
||||
(
|
||||
{
|
||||
currency: 'Moeda do projecto',
|
||||
expected_start_date:
|
||||
'Data prevista de início',
|
||||
entity_stage:
|
||||
'Situação da entidade',
|
||||
} as Record<string, string>
|
||||
)[key] ??
|
||||
key,
|
||||
)
|
||||
).join('; ')}
|
||||
.
|
||||
</p>
|
||||
)}
|
||||
{!data.period_covered && (
|
||||
<p>
|
||||
Este incremento cobre pedidos com início previsto em
|
||||
2026. Uma data em falta ou fora desse recorte não
|
||||
permite determinar o pedido por este lote; os
|
||||
restantes trabalhos do dossier continuam
|
||||
disponíveis.
|
||||
</p>
|
||||
)}
|
||||
{!data.scope_covered && (
|
||||
<p>
|
||||
Declare a actividade efectiva. Prospecção,
|
||||
pesquisa/produção extractiva e investimento público
|
||||
ou exclusivamente social/sem fins lucrativos seguem
|
||||
enquadramentos próprios, fora deste lote.
|
||||
</p>
|
||||
)}
|
||||
{data.reason.map((reason) => (
|
||||
<p key={reason}>{reason}</p>
|
||||
))}
|
||||
<p>
|
||||
Encaminhamento indicativo: {data.authority}.{' '}
|
||||
<a
|
||||
className="underline"
|
||||
href={data.authority_source.url}
|
||||
>
|
||||
{data.authority_source.reference}
|
||||
</a>
|
||||
</p>
|
||||
<ul className="space-y-3">
|
||||
{data.requirements
|
||||
.filter((r) => r.state !== 'not_applicable')
|
||||
.map((r) => (
|
||||
<li key={r.key} className="rounded border p-3">
|
||||
<strong>{r.label}</strong> —{' '}
|
||||
{states[r.state]}.{' '}
|
||||
{r.document?.file_name && (
|
||||
<span>
|
||||
Ficheiro: {r.document.file_name}
|
||||
.{' '}
|
||||
</span>
|
||||
)}
|
||||
<p>
|
||||
{r.reason}{' '}
|
||||
<a
|
||||
className="underline"
|
||||
href={r.source.url}
|
||||
>
|
||||
Ver fundamento: {r.source.reference}
|
||||
</a>{' '}
|
||||
· {r.source.period}.
|
||||
</p>
|
||||
{caseId && (
|
||||
<Link
|
||||
className="underline"
|
||||
href={`/investimentos/${caseId}/contexto/editar#preparation-documents`}
|
||||
>
|
||||
Rever dados ou associar documento do
|
||||
dossier
|
||||
</Link>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<details>
|
||||
<summary>
|
||||
Exigências que não se aplicam — histórico preservado
|
||||
</summary>
|
||||
{data.requirements
|
||||
.filter((r) => r.state === 'not_applicable')
|
||||
.map((r) => (
|
||||
<p key={r.key}>
|
||||
{r.label}: {r.reason}{' '}
|
||||
{r.document_id && (
|
||||
<>
|
||||
Associação anterior mantida
|
||||
{r.document?.file_name
|
||||
? `: ${r.document.file_name}`
|
||||
: ''}
|
||||
.
|
||||
</>
|
||||
)}{' '}
|
||||
<a
|
||||
className="underline"
|
||||
href={r.source.url}
|
||||
>
|
||||
{r.source.reference}
|
||||
</a>
|
||||
</p>
|
||||
))}
|
||||
</details>
|
||||
<p>{data.calculation_notice}</p>
|
||||
<p>{data.notice}</p>
|
||||
</>
|
||||
)}
|
||||
<a className="underline" href={data.source.url}>
|
||||
Consultar o exemplar oficial e os fundamentos
|
||||
</a>
|
||||
{caseId && (
|
||||
<p>
|
||||
<Link
|
||||
className="underline"
|
||||
href={`/investimentos/${caseId}/contexto/editar#preparation-questions`}
|
||||
>
|
||||
Completar apenas os dados em falta ou editar declarações
|
||||
</Link>
|
||||
</p>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export function PreparationEditor({
|
||||
data,
|
||||
fields,
|
||||
changes,
|
||||
documents,
|
||||
links,
|
||||
onChange,
|
||||
onLink,
|
||||
disabled,
|
||||
}: {
|
||||
data: Preparation;
|
||||
fields: Record<string, string>;
|
||||
changes: Record<string, string>;
|
||||
documents: PreparationDocument[];
|
||||
links: Record<string, string | null>;
|
||||
onChange: (key: string, value: string) => void;
|
||||
onLink: (key: string, value: string | null) => void;
|
||||
disabled: boolean;
|
||||
}) {
|
||||
const values = { ...fields, ...changes };
|
||||
const active = values.investment_preparation === 'yes';
|
||||
const excluded = ['extractive', 'public_nonprofit'].includes(
|
||||
values.investment_activity_scope,
|
||||
);
|
||||
const applicable = (q: Preparation['questions'][number]) =>
|
||||
!q.when || values[q.when.field] === q.when.equals;
|
||||
// Keep a field in place while the user edits it; collapse only recovered answers.
|
||||
const known = (q: Preparation['questions'][number]) =>
|
||||
!!fields[q.key] && fields[q.key] !== 'unknown';
|
||||
const render = (q: Preparation['questions'][number]) => (
|
||||
<label className="grid gap-1" key={q.key} htmlFor={'prep-' + q.key}>
|
||||
{q.label}
|
||||
{q.options ? (
|
||||
<select
|
||||
id={'prep-' + q.key}
|
||||
className={input}
|
||||
disabled={disabled}
|
||||
value={values[q.key] ?? ''}
|
||||
onChange={(e) => onChange(q.key, e.target.value)}
|
||||
>
|
||||
<option value="">Por indicar</option>
|
||||
{Object.entries(q.options).map(([value, text]) => (
|
||||
<option key={value} value={value}>
|
||||
{text}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
) : (
|
||||
<input
|
||||
id={'prep-' + q.key}
|
||||
className={input}
|
||||
disabled={disabled}
|
||||
type="text"
|
||||
inputMode="decimal"
|
||||
value={values[q.key] ?? ''}
|
||||
onChange={(e) => onChange(q.key, e.target.value)}
|
||||
/>
|
||||
)}
|
||||
<span className="text-sm">
|
||||
{q.help} Fundamento: {q.source}.
|
||||
</span>
|
||||
</label>
|
||||
);
|
||||
const questions = data.questions.filter(
|
||||
(q) =>
|
||||
q.key !== 'investment_preparation' &&
|
||||
applicable(q) &&
|
||||
(!excluded || q.key === 'investment_activity_scope'),
|
||||
);
|
||||
const needed = questions.filter(
|
||||
(q) =>
|
||||
!known(q) &&
|
||||
(!data.enabled ||
|
||||
data.mode !== 'authorization' ||
|
||||
data.missing_fields.includes(q.key)),
|
||||
);
|
||||
return (
|
||||
<section
|
||||
id="preparation-questions"
|
||||
className={card + ' space-y-4'}
|
||||
aria-label="Dados do pedido de investimento privado"
|
||||
>
|
||||
<h2 className="text-xl font-bold">
|
||||
Dados em falta para preparar o pedido
|
||||
</h2>
|
||||
<p>
|
||||
Valores conhecidos são reutilizados. As alterações só são
|
||||
gravadas depois da comparação de impacto. Nacionalidade e
|
||||
residência não determinam a origem do capital nem a modalidade
|
||||
da empresa.
|
||||
</p>
|
||||
{render(data.questions[0])}
|
||||
{active && (
|
||||
<>
|
||||
{needed.map(render)}
|
||||
<details>
|
||||
<summary>
|
||||
Dados já declarados — consultar ou alterar
|
||||
</summary>
|
||||
<div className="space-y-3">
|
||||
{questions.filter(known).map(render)}
|
||||
</div>
|
||||
</details>
|
||||
<p>
|
||||
Data, moeda e situação da entidade usam as declarações
|
||||
acima.{' '}
|
||||
{data.missing_fields.filter((k) =>
|
||||
[
|
||||
'currency',
|
||||
'expected_start_date',
|
||||
'entity_stage',
|
||||
].includes(k),
|
||||
).length > 0 &&
|
||||
'Complete esses campos em falta antes de comparar.'}
|
||||
</p>
|
||||
<div id="preparation-documents" className="space-y-3">
|
||||
<h3 className="font-bold">
|
||||
Associar documentos já existentes no dossier
|
||||
</h3>
|
||||
<p>
|
||||
Carregue documentos pelo Balcão Virtual do mesmo
|
||||
dossier. A associação não aprova o conteúdo nem
|
||||
substitui a análise do pedido.
|
||||
</p>
|
||||
{data.requirements
|
||||
.filter((r) => r.required)
|
||||
.map((r) => (
|
||||
<label className="grid gap-1" key={r.key}>
|
||||
{r.label}
|
||||
<select
|
||||
className={input}
|
||||
disabled={disabled}
|
||||
value={
|
||||
Object.hasOwn(links, r.key)
|
||||
? (links[r.key] ?? '')
|
||||
: (r.document_id ?? '')
|
||||
}
|
||||
onChange={(e) =>
|
||||
onLink(
|
||||
r.key,
|
||||
e.target.value || null,
|
||||
)
|
||||
}
|
||||
>
|
||||
<option value="">Por associar</option>
|
||||
{documents.map((doc) => (
|
||||
<option
|
||||
key={doc.document_id}
|
||||
value={doc.document_id}
|
||||
>
|
||||
{doc.file_name} ·{' '}
|
||||
{{
|
||||
quarantined:
|
||||
'Em quarentena',
|
||||
active: 'Disponível',
|
||||
deleted: 'Removido',
|
||||
}[doc.status] ??
|
||||
'Estado por conferir'}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<span>
|
||||
{r.reason}{' '}
|
||||
<a
|
||||
className="underline"
|
||||
href={r.source.url}
|
||||
>
|
||||
{r.source.reference}
|
||||
</a>
|
||||
</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<p>
|
||||
Compare o impacto para actualizar o enquadramento, as perguntas
|
||||
e as exigências. Uma exigência que deixe de ser aplicável
|
||||
conserva o documento anterior no histórico.
|
||||
</p>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export function PreparationImpact({
|
||||
before,
|
||||
after,
|
||||
}: {
|
||||
before: Preparation;
|
||||
after: Preparation;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className="space-y-2 rounded border border-amber-400 p-3"
|
||||
aria-label="Impacto no pedido de investimento"
|
||||
>
|
||||
<h3 className="font-bold">Impacto no pedido e nos documentos</h3>
|
||||
<p>
|
||||
{preparationMode(before.mode)} → {preparationMode(after.mode)}.
|
||||
</p>
|
||||
<p>Próximo passo: {after.next}.</p>
|
||||
<ul className="list-disc pl-5">
|
||||
{after.requirements
|
||||
.filter(
|
||||
(r) =>
|
||||
before.requirements.find((b) => b.key === r.key)
|
||||
?.state !== r.state,
|
||||
)
|
||||
.map((r) => (
|
||||
<li key={r.key}>
|
||||
{r.label}:{' '}
|
||||
{
|
||||
states[
|
||||
before.requirements.find(
|
||||
(b) => b.key === r.key,
|
||||
)?.state ?? 'indeterminate'
|
||||
]
|
||||
}{' '}
|
||||
→ {states[r.state]}.{' '}
|
||||
{r.document_id && 'Vínculo documental preservado.'}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<p>{after.calculation_notice}</p>
|
||||
<PreparationSummary data={after} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user