This commit is contained in:
2026-11-11 11:53:41 +02:00
parent 2209b83820
commit 1b8b9f414a
32 changed files with 36011 additions and 43 deletions
+8
View File
@@ -1,3 +1,11 @@
## Comunicação pública — orientação expressa do proprietário,11/11/2026
Apresentar conteúdos públicos com linguagem profissional,clara e apelativa,centrada na entidade,oportunidade,produtos e serviços úteis ao investidor. Não usar descrições de importação,cadastro,fichas,ficheiros ou documentos geradores como apresentação comercial. Preservar as fontes,versões e rastreabilidade no cadastro/editor;ligações institucionais e datas úteis podem ser consultadas separadamente. Não inventar ofertas,condições,disponibilidade,cobertura ou garantias para preencher lacunas. Esta orientação aplica-se transversalmente aos conteúdos do portal.
## Simulação de viabilidade — clarificação expressa do proprietário,11/11/2026
O InvesteMoz simula impostos para avaliar a viabilidade do negócio; não actua como entidade que executa pagamentos. Apresentar PPC/PEC e distribuição por meses como estimativas fiscais e projecções de tesouraria,com fontes e pressupostos. Montantes já pagos,quando fornecidos,são dados documentais para o cálculo. Não confundir uma hipótese de caixa com pagamento efectuado,declaração submetida ou cumprimento fiscal certificado. Preservar os cálculos,históricos e dados aceites; não criar serviços de cobrança/execução de pagamentos como requisito de conclusão deste simulador.
# Instruções permanentes do projeto InvesteMoz
## Encerramento e reabertura do dossier — decisão do proprietário, 03/11/2026
@@ -26,13 +26,27 @@ class TranslationApiController extends Controller
public function translate(Request $request): JsonResponse
{
$data = $request->validate([
'text' => 'required|string|max:10000',
'text' => is_array($request->input('text'))
? 'required|array|min:1|max:8'
: 'required|string|max:10000',
'text.*' => 'required|string|max:10000',
'source_lang' => 'required|string|max:20',
'target_lang' => 'required|string|max:20',
'is_html' => 'boolean',
]);
$isBatch = is_array($data['text']);
if ($isBatch && mb_strlen(implode('', $data['text'])) > 10000) {
return response()->json(['message' => 'O lote de tradução excede o limite permitido.'], 422);
}
if ($data['source_lang'] === $data['target_lang']) {
return response()->json(['original_text' => $data['text'], 'translated_text' => $data['text'], 'source_lang' => $data['source_lang'], 'target_lang' => $data['target_lang']])->header('Cache-Control', 'no-store');
return response()->json([
'original_text' => $data['text'],
'translated_text' => $data['text'],
'source_lang' => $data['source_lang'],
'target_lang' => $data['target_lang'],
])->header('Cache-Control', 'no-store');
}
$payload = ['q' => $data['text'], 'source' => $data['source_lang'], 'target' => $data['target_lang'], 'format' => ($data['is_html'] ?? false) ? 'html' : 'text'];
@@ -42,8 +56,19 @@ class TranslationApiController extends Controller
try {
$response = Http::connectTimeout(2)->timeout(config('services.translation.timeout'))->post($this->url().'/translate', $payload);
$translated = $response->json('translatedText');
if ($response->successful() && is_string($translated) && $translated !== '') {
return response()->json(['original_text' => $data['text'], 'translated_text' => $translated, 'source_lang' => $data['source_lang'], 'target_lang' => $data['target_lang']])->header('Cache-Control', 'no-store');
$validTranslation = $isBatch
? is_array($translated)
&& count($translated) === count($data['text'])
&& ! array_filter($translated, fn ($text) => ! is_string($text) || $text === '')
: is_string($translated) && $translated !== '';
if ($response->successful() && $validTranslation) {
return response()->json([
'original_text' => $data['text'],
'translated_text' => $translated,
'source_lang' => $data['source_lang'],
'target_lang' => $data['target_lang'],
])->header('Cache-Control', 'no-store');
}
} catch (Throwable) {
}
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Models\Opportunity;
use App\Services\InvestmentGeography;
use App\Services\SectorOrganization;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
@@ -20,8 +21,9 @@ class HomeController extends Controller
$locale = $validated['locale'] ?? 'pt';
$data = Cache::remember("portal_home_sector_organization_v1_{$locale}", now()->addMinutes(10), function () {
$data = Cache::remember("portal_home_territorial_v2_{$locale}", now()->addMinutes(10), function () {
$sectors = SectorOrganization::catalog();
$statistics = app(InvestmentGeography::class)->opportunityStatistics();
$featured = Opportunity::with('sector:id,name,slug')
->published()->where('is_featured', true)
@@ -48,9 +50,9 @@ class HomeController extends Controller
'url' => "/oportunidades/{$o->slug}",
])->values()->all(),
'stats' => [
'opportunities' => Opportunity::published()->count(),
'opportunities' => $statistics['opportunities'],
'sectors' => $sectors->count(),
'provinces' => Opportunity::published()->distinct('province')->count('province'),
'provinces' => $statistics['provinces'],
],
];
});
@@ -21,11 +21,13 @@ use App\Services\EducationWorkQueue;
use App\Services\InvestmentGeography;
use App\Services\PortalAudit;
use Carbon\CarbonImmutable;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
use Inertia\Inertia;
use Inertia\Response;
@@ -166,6 +168,7 @@ class EducationController extends Controller
Inertia::encryptHistory();
return tap(Inertia::render('education/declaration', ['declaration' => $row, 'kind' => $kind, 'actorId' => $request->user()->id, 'today' => now()->toDateString(),
'preparation' => app(EducationReviews::class)->preparation($request->user(), $record),
'history' => app(EducationReviews::class)->history($request->user(), $record),
'backUrl' => route('management.education.index', $filters, false), 'references' => $references])->toResponse($request), static fn (\Symfony\Component\HttpFoundation\Response $response) => $response->headers->set('Cache-Control', 'private, no-store'));
}
@@ -203,6 +206,29 @@ class EducationController extends Controller
return back()->with('success', 'Declaração registada para revisão independente.');
}
public function reviewPreparation(Request $request, string $kind, string $id): JsonResponse
{
$service = app(EducationReviews::class);
app(EducationAccess::class)->authorize($request->user());
if ($request->isMethod('POST')) {
// Like CRM preparations, retain partial text before business validation.
$input = json_decode($request->getContent(), true);
if (! is_array($input)) {
throw ValidationException::withMessages(['snapshot' => 'A preparação deve ser enviada em JSON.']);
}
$view = $service->savePreparation($request->user(), $kind, $id, $input);
} else {
$expected = $request->validate(['actor_id' => 'sometimes|required|integer|min:1']);
abort_if(isset($expected['actor_id']) && (int) $expected['actor_id'] !== $request->user()->id, 409, 'A conta mudou. Actualize a entrada antes de recuperar.');
$record = match ($kind) {
'demands' => LabourDemand::findOrFail($id), 'capacities' => TrainingCapacity::findOrFail($id), default => abort(404)
};
$view = $service->preparation($request->user(), $record);
}
return response()->json($view)->header('Cache-Control', 'private, no-store');
}
public function review(Request $request, string $kind, string $id): RedirectResponse
{
$result = app(EducationReviews::class)->decide($request->user(), $kind, $id, $request->all());
@@ -4,10 +4,16 @@ namespace App\Http\Controllers;
use App\Models\Sector;
use App\Services\DocumentManagementAccess;
use App\Services\FundingDossierPreparation;
use App\Services\FundingInstitutionClassification;
use App\Services\FundingInstitutionDetails;
use App\Services\FundingProgrammeReferences;
use App\Services\FundingProgrammes;
use App\Services\InvestmentGeography;
use App\Services\InvestorContext;
use App\Services\InvestorPath;
use App\Services\Procurement;
use App\Services\VirtualDesk;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
@@ -22,20 +28,38 @@ final class FundingProgrammeController extends Controller
{
public function reference(Request $request, string $code, FundingProgrammeReferences $references): \Symfony\Component\HttpFoundation\Response
{
return tap(Inertia::render('funding/reference', ['reference' => $references->preview($request->user(), $code)])->toResponse($request),
$plan = $references->preview($request->user(), $code);
if ($request->expectsJson()) {
return response()->json($plan)->header('Cache-Control', 'private, no-store');
}
return tap(Inertia::render('funding/reference', ['reference' => $plan])->toResponse($request),
static fn (\Symfony\Component\HttpFoundation\Response $response) => $response->headers->set('Cache-Control', 'private, no-store'));
}
public function prepareReference(Request $request, string $code, FundingProgrammeReferences $references): RedirectResponse
public function prepareReference(Request $request, string $code, FundingProgrammeReferences $references): RedirectResponse|JsonResponse
{
$data = $request->validate(['hash' => 'required|string|size:64']);
$result = $references->prepare($request->user(), $code, $data['hash']);
if ($request->expectsJson()) {
$reference = $references->preview($request->user(), $code);
$service = app(FundingProgrammes::class);
$matches = $service->matchesReference($request->user(), $result['id'], $reference['draft']);
$publication = $matches ? $service->preview($request->user(), $result['id']) : null;
return response()->json([...$result, 'matches_reference' => $matches, 'publication' => $publication ? ['version' => $publication['version'], 'hash' => $publication['hash']] : null])->header('Cache-Control', 'private, no-store');
}
return redirect('/management/fundos/'.$result['id'])->with('success', $result['created']
? 'Rascunho preparado com as fontes indicadas. Ainda não está publicado.'
: 'Esta referência já tem ficha. Aberta a versão existente, sem substituir alterações ou acrescentar chamadas.');
}
public function assisted(Request $request, FundingProgrammeReferences $references): \Symfony\Component\HttpFoundation\Response
{
return tap(Inertia::render('funding/assisted', ['batch' => $references->assisted($request->user())])->toResponse($request), static fn ($response) => $response->headers->set('Cache-Control', 'private, no-store'));
}
public function manage(Request $request, FundingProgrammes $service): \Symfony\Component\HttpFoundation\Response
{
$service->guard($request->user());
@@ -43,7 +67,7 @@ final class FundingProgrammeController extends Controller
$items->through(fn ($row) => ['id' => $row->id, 'title' => json_decode($row->draft, true)['title'] ?? 'Rascunho sem título',
'version' => (int) $row->version, 'published' => $row->published_version_id !== null]);
return tap(Inertia::render('funding/manage', ['items' => $items])->toResponse($request),
return tap(Inertia::render('funding/manage', ['items' => $items, 'references' => app(FundingProgrammeReferences::class)->choices($request->user())])->toResponse($request),
static fn (\Symfony\Component\HttpFoundation\Response $response) => $response->headers->set('Cache-Control', 'private, no-store'));
}
@@ -63,6 +87,7 @@ final class FundingProgrammeController extends Controller
return tap(Inertia::render('funding/edit', ['record' => $row ? ['id' => $row->id, 'code' => $row->code, 'version' => (int) $row->version,
'draft' => json_decode($row->draft, true), 'published' => $row->published_version_id !== null] : null,
'references' => Arr::only($references, ['organizations', 'sectors', 'territories', 'documents']),
'categories' => FundingInstitutionClassification::CATEGORIES, 'institutionTypes' => FundingInstitutionClassification::TYPES, 'institutionNatures' => FundingInstitutionDetails::NATURES, 'needs' => FundingInstitutionDetails::NEEDS,
'kinds' => FundingProgrammes::KINDS, 'windowModes' => FundingProgrammes::WINDOWS,
'history' => $id ? DB::table('funding_programme_versions')->where('programme_id', $id)->orderByDesc('version')->get(['version', 'reason', 'published_at']) : []])->toResponse($request),
static fn (\Symfony\Component\HttpFoundation\Response $response) => $response->headers->set('Cache-Control', 'private, no-store'));
@@ -80,9 +105,12 @@ final class FundingProgrammeController extends Controller
return response()->json($service->preview($request->user(), $id))->header('Cache-Control', 'private, no-store');
}
public function publish(Request $request, string $id, FundingProgrammes $service): RedirectResponse
public function publish(Request $request, string $id, FundingProgrammes $service): RedirectResponse|JsonResponse
{
$service->publish($request->user(), $id, $request->all());
$versionId = $service->publish($request->user(), $id, $request->all());
if ($request->expectsJson()) {
return response()->json(['id' => $id, 'version_id' => $versionId, 'published' => true])->header('Cache-Control', 'private, no-store');
}
return back()->with('success', 'Nova versão publicada. Os interesses anteriores conservam a versão guardada.');
}
@@ -118,15 +146,22 @@ final class FundingProgrammeController extends Controller
public function index(Request $request, FundingProgrammes $service): \Symfony\Component\HttpFoundation\Response
{
$filters = $request->validate(['q' => 'nullable|string|max:150', 'kind' => 'nullable|in:fund,programme,incentive',
$filters = $request->validate(['q' => 'nullable|string|max:150', 'kind' => 'nullable|in:institution,fund,programme,incentive',
'category' => ['nullable', Rule::in(array_keys(FundingInstitutionClassification::CATEGORIES))], 'institution_type' => ['nullable', Rule::in(array_keys(FundingInstitutionClassification::TYPES))], 'location_mode' => 'nullable|in:physical,coverage', 'include_historical' => 'nullable|boolean',
'need' => ['nullable', Rule::in(array_keys(FundingInstitutionDetails::NEEDS))],
'context' => 'nullable|uuid', 'opportunity' => 'nullable|string|max:150',
'territory' => ['nullable', 'uuid', Rule::in(app(InvestmentGeography::class)->publicReferences()->pluck('id')->all())],
'sector' => ['nullable', 'integer', Rule::in(Sector::choices()->whereNotNull('published_at')->pluck('id')->all())],
'certainty' => 'nullable|in:include_unknown,documented', 'state' => 'nullable|in:open,closed,upcoming,unknown']);
$filters = array_filter($filters, fn ($value) => $value !== null && $value !== '');
$dossier = $request->user() && ! empty($filters['context']) ? app(FundingDossierPreparation::class)->context($request->user(), $filters['context'], $filters['opportunity'] ?? null) : null;
if ($dossier && empty($filters['territory']) && $dossier['territory']) {
$filters['territory'] = $dossier['territory'];
}
$query = $service->catalogueQuery($filters);
if (! empty($filters['q'])) {
$term = '%'.str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], $filters['q']).'%';
$query->where(fn ($q) => $q->whereRaw("v.payload->>'title' ilike ?", [$term])->orWhereRaw("v.payload->>'summary' ilike ?", [$term]));
$query->where(fn ($q) => $q->whereRaw("v.payload->>'title' ilike ?", [$term])->orWhereRaw("v.payload->>'summary' ilike ?", [$term])->orWhereRaw("(v.payload->'aliases')::text ilike ?", [$term])->orWhereRaw("EXISTS (SELECT 1 FROM jsonb_array_elements(COALESCE(v.payload->'locations','[]'::jsonb)) l WHERE l->>'label' ilike ? OR l->>'address' ilike ? OR l->>'location_text' ilike ?)", [$term, $term, $term]));
}
if (! empty($filters['kind'])) {
$query->whereRaw("v.payload->>'kind' = ?", [$filters['kind']]);
@@ -138,7 +173,7 @@ final class FundingProgrammeController extends Controller
return [...$record, 'filter_explanation' => $service->filterExplanation($record, $filters)];
});
return tap(Inertia::render('funding/index', ['items' => $items, 'filters' => $filters, 'kinds' => FundingProgrammes::KINDS,
return tap(Inertia::render('funding/index', ['items' => $items, 'filters' => $filters, 'categories' => FundingInstitutionClassification::CATEGORIES, 'institutionTypes' => FundingInstitutionClassification::TYPES, 'kinds' => FundingProgrammes::KINDS, 'needs' => FundingInstitutionDetails::NEEDS, 'dossier' => $dossier,
'options' => ['territories' => app(InvestmentGeography::class)->publicReferences()->orderBy('name')->get(['id', 'name']),
'sectors' => Sector::choices()->whereNotNull('published_at')->orderBy('name')->get(['id', 'name'])], 'management' => $service->access($request->user())])
->toResponse($request), static fn (\Symfony\Component\HttpFoundation\Response $response) => $response->headers->set('Cache-Control', 'private, no-store'));
@@ -146,14 +181,38 @@ final class FundingProgrammeController extends Controller
public function show(Request $request, string $id, FundingProgrammes $service): \Symfony\Component\HttpFoundation\Response
{
$request->validate(['context' => 'nullable|uuid', 'opportunity' => 'nullable|string|max:150', 'need' => ['nullable', Rule::in(array_keys(FundingInstitutionDetails::NEEDS))], 'territory' => 'nullable|uuid', 'category' => 'nullable|string|max:80', 'institution_type' => 'nullable|string|max:80', 'location_mode' => 'nullable|in:physical,coverage']);
$dossier = $request->user() && $request->filled('context') ? app(FundingDossierPreparation::class)->context($request->user(), $request->string('context')->toString(), $request->query('opportunity')) : null;
if ($dossier && ! $request->filled('territory') && $dossier['territory']) {
$request->merge(['territory' => $dossier['territory']]);
}
$record = $service->published($id);
$destinations = [];
if ($request->user()) {
foreach (app(InvestorPath::class)->contexts($request->user()) as $item) {
$case = app(VirtualDesk::class)->case($request->user(), $item['id']);
$state = app(InvestorContext::class)->state($case);
$choices = $state['opportunity_choices'] ?? [];
if ($choices === []) {
$destinations[] = ['id' => $item['id'], 'opportunity' => null, 'title' => $item['title']];
}
foreach ($choices as $choice) {
$destinations[] = ['id' => $item['id'], 'opportunity' => $choice['opportunity']['key'], 'title' => $item['title'].' — '.$choice['opportunity']['title']];
}
}
}
$saved = $request->user() ? DB::table('funding_programme_interests as i')->join('funding_programme_versions as v', 'v.id', '=', 'i.version_id')
->where('i.programme_id', $id)->where('i.user_id', $request->user()->id)->whereNull('i.removed_at')->first(['v.version', 'i.saved_at']) : null;
return tap(Inertia::render('funding/show', ['record' => $record, 'saved' => $saved, 'authenticated' => $request->user() !== null])
return tap(Inertia::render('funding/show', ['record' => $record, 'saved' => $saved, 'authenticated' => $request->user() !== null, 'dossier' => $dossier, 'selection' => [...$request->only(['need', 'territory', 'context', 'opportunity', 'category', 'institution_type', 'location_mode']), 'territory_label' => app(InvestmentGeography::class)->publicReferences()->where('id', $request->query('territory'))->value('name') ?? 'Local por seleccionar'], 'needs' => FundingInstitutionDetails::NEEDS, 'destinations' => $destinations])
->toResponse($request), static fn (\Symfony\Component\HttpFoundation\Response $response) => $response->headers->set('Cache-Control', 'private, no-store'));
}
public function dossier(Request $request, string $id, FundingDossierPreparation $service): JsonResponse
{
return response()->json($service->save($request->user(), $id, $request->all()))->header('Cache-Control', 'private, no-store');
}
public function assess(Request $request, string $id, FundingProgrammes $service): JsonResponse
{
return response()->json($service->assess($id, $request->all()))->header('Cache-Control', 'private, no-store');
@@ -0,0 +1,50 @@
<?php
namespace App\Http\Controllers;
use App\Services\InvestmentPreparationWorkspace;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
final class InvestmentPreparationController extends Controller
{
public function show(Request $request, string $id, InvestmentPreparationWorkspace $workspace): JsonResponse|Response
{
$request->validate(['opportunity' => 'nullable|string|max:150', 'history_scope' => 'nullable|in:dossier', 'history_before' => 'nullable|integer|min:1']);
$data = $workspace->view($request->user(), $id, $request->query('opportunity'), $request->filled('history_before') ? $request->integer('history_before') : null, $request->query('history_scope') === 'dossier');
return $request->expectsJson() ? response()->json($data) : Inertia::render('investor-path/preparation', ['workspace' => $data]);
}
public function change(Request $request, string $id, InvestmentPreparationWorkspace $workspace): JsonResponse
{
return response()->json($workspace->change($request->user(), $id, $request->all()));
}
public function preview(Request $request, string $id, InvestmentPreparationWorkspace $workspace): \Illuminate\Http\Response
{
$request->validate(['opportunity' => 'nullable|string|max:150', 'history_scope' => 'nullable|in:dossier']);
$data = $workspace->view($request->user(), $id, $request->query('opportunity'), null, $request->query('history_scope') === 'dossier');
return response()->view('investments.preparation-index', ['workspace' => $data, 'manifest' => null])->header('Content-Security-Policy', "sandbox; default-src 'none'; style-src 'unsafe-inline'")->header('X-Content-Type-Options', 'nosniff');
}
public function download(Request $request, string $id, string $exportId, InvestmentPreparationWorkspace $workspace): \Illuminate\Http\Response
{
$request->validate(['opportunity' => 'nullable|string|max:150', 'history_scope' => 'nullable|in:dossier']);
$bundle = $workspace->bundle($request->user(), $id, $request->query('opportunity'), $exportId, $request->query('history_scope') === 'dossier');
return response($bundle['bytes'], 200, ['Content-Type' => 'application/zip', 'Content-Disposition' => 'attachment; filename="preparacao-apiex-'.$id.'.zip"', 'X-Export-Id' => $exportId, 'X-Content-Type-Options' => 'nosniff']);
}
public function export(Request $request, string $id, InvestmentPreparationWorkspace $workspace): \Illuminate\Http\Response
{
$body = [...$request->all(), 'action' => 'apiex_export'];
$state = $workspace->change($request->user(), $id, $body);
$bundle = $workspace->bundle($request->user(), $id, $state['opportunity'], $body['operation_key']);
return response($bundle['bytes'], 200, ['Content-Type' => 'application/zip', 'Content-Disposition' => 'attachment; filename="preparacao-apiex-'.$id.'.zip"', 'X-Export-Id' => $bundle['export_id'], 'X-Content-Type-Options' => 'nosniff']);
}
}
@@ -0,0 +1,30 @@
<?php
namespace App\Http\Controllers;
use App\Services\OpportunityCuration;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
class OpportunityCurationController extends Controller
{
public function show(Request $request, int $id, OpportunityCuration $service): Response
{
$base = str_starts_with($request->path(), 'minhas-oportunidades') ? '/minhas-oportunidades' : '/management/opportunities';
$filters = $request->validate(['page' => 'nullable|integer|min:1|max:1000000']);
return Inertia::render('management/opportunity-curation', [...$service->state($request->user(), $id, (int) ($filters['page'] ?? 1)), 'basePath' => $base]);
}
public function preview(Request $request, int $id, OpportunityCuration $service): JsonResponse
{
return response()->json($service->preview($request->user(), $id, $request->all()))->header('Cache-Control', 'private, no-store');
}
public function confirm(Request $request, int $id, OpportunityCuration $service): JsonResponse
{
return response()->json($service->confirm($request->user(), $id, $request->all()))->header('Cache-Control', 'private, no-store');
}
}
@@ -8,6 +8,7 @@ use App\Models\Sector;
use App\Services\InvestmentGeography;
use App\Services\OpportunityCanonicalContract;
use App\Services\OpportunityCanonicalResponse;
use App\Services\OpportunityCuration;
use App\Services\OpportunityManagement;
use App\Services\OpportunityManagementInstallation;
use App\Services\OpportunityMaturity;
@@ -130,6 +131,8 @@ class OpportunityManagementController extends Controller
$access->authorize($request->user(), 'view', $record);
}
Inertia::share(['basePath' => str_starts_with($request->path(), 'minhas-oportunidades') ? '/minhas-oportunidades' : '/management/opportunities',
'curationHref' => $record && app(OpportunityCuration::class)->allowed($request->user(), $record)
? (str_starts_with($request->path(), 'minhas-oportunidades') ? '/minhas-oportunidades/' : '/management/opportunities/').$record->id.'/destaque' : null,
'canonicalOptions' => ($record?->owner_user_id !== null || str_starts_with($request->path(), 'minhas-oportunidades')) ? app(OpportunityCanonicalContract::class)->options($request->user(), $record) : null,
'workflowState' => $record ? $access->state($record) : null,
'profileReauthenticationRequired' => $record && $record->owner_user_id !== null
@@ -0,0 +1,67 @@
<?php
namespace App\Http\Controllers;
use App\Models\Opportunity;
use App\Support\PublicSitemapXml;
use Generator;
use Symfony\Component\HttpFoundation\StreamedResponse;
class PublicSitemapController extends Controller
{
private const PAGE_SIZE = 1000;
public function index(PublicSitemapXml $xml): StreamedResponse
{
$base = $this->base();
$pages = (int) ceil(Opportunity::published()->count() / self::PAGE_SIZE);
$locations = (static function () use ($base, $pages): Generator {
yield $base.'/sitemap/pages.xml';
for ($page = 1; $page <= $pages; $page++) {
yield $base.'/sitemap/opportunities/'.$page.'.xml';
}
})();
return $this->response($xml, $locations, true);
}
public function pages(PublicSitemapXml $xml): StreamedResponse
{
$base = $this->base();
$paths = ['/', '/oportunidades', '/sectores', '/como-investir', '/documentos', '/apoio', '/simulador-fiscal', '/politica-de-privacidade', '/termos-de-uso'];
return $this->response($xml, array_map(fn ($path) => $base.$path, $paths));
}
public function opportunities(string $page, PublicSitemapXml $xml): StreamedResponse
{
$number = filter_var($page, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1, 'max_range' => 1000000]]);
abort_if($number === false, 404);
$base = $this->base();
$records = Opportunity::published()->orderBy('id')->offset(($number - 1) * self::PAGE_SIZE)->limit(self::PAGE_SIZE)->get(['slug']);
abort_if($records->isEmpty(), 404);
return $this->response($xml, $records->map(fn ($record) => $base.'/oportunidades/'.rawurlencode($record->slug)));
}
private function base(): string
{
$url = rtrim((string) config('app.url'), '/');
$parts = parse_url($url);
abort_unless(filter_var($url, FILTER_VALIDATE_URL) && is_array($parts)
&& in_array($parts['scheme'] ?? null, ['http', 'https'], true)
&& ! isset($parts['query']) && ! isset($parts['fragment']) && ! isset($parts['user']) && ! isset($parts['pass']), 503);
return $url;
}
/** @param iterable<string> $locations */
private function response(PublicSitemapXml $xml, iterable $locations, bool $index = false): StreamedResponse
{
return response()->stream(static function () use ($xml, $locations, $index): void {
foreach ($xml->chunks($locations, $index) as $chunk) {
echo $chunk;
}
}, 200, ['Content-Type' => 'application/xml; charset=UTF-8', 'Cache-Control' => 'no-store', 'X-Content-Type-Options' => 'nosniff']);
}
}
+18 -1
View File
@@ -78,6 +78,23 @@ class VirtualDeskController extends Controller
}
public function showCase(Request $request, string $id, VirtualDesk $desk): JsonResponse|Response
{
$data = $this->caseViewData($request, $id, $desk);
return $request->expectsJson() ? response()->json($data) : Inertia::render('desk/case', $data);
}
public function preDossier(Request $request, string $id, VirtualDesk $desk): \Illuminate\Http\Response
{
return response()->view('desk.pre-dossier', $this->caseViewData($request, $id, $desk), 200, [
'Cache-Control' => 'private, no-store', 'X-Content-Type-Options' => 'nosniff',
]);
}
/** Same authorised projection for the workspace and its preparation document.
* @return array<string,mixed>
*/
private function caseViewData(Request $request, string $id, VirtualDesk $desk): array
{
$case = $desk->case($request->user(), $id);
$owner = $desk->owns($request->user(), $case, false);
@@ -98,7 +115,7 @@ class VirtualDeskController extends Controller
'links' => DB::table('desk_institutional_links')->get()->map(fn ($link) => [...(array) $link, 'monitor' => app(InstitutionalLinkMonitor::class)->status($link)]), 'source' => config('virtual-desk.source'), 'support' => config('portal.support'),
'guidance' => app(InvestorGuidance::class)->installed() ? app(InvestorGuidance::class)->view($request->user(), $id) : null];
return $request->expectsJson() ? response()->json($data) : Inertia::render('desk/case', $data);
return $data;
}
/** @return Collection<int,\stdClass> */
+2
View File
@@ -81,6 +81,8 @@ class AppServiceProvider extends ServiceProvider
Cache::forget('portal_home_v2_en');
Cache::forget('portal_home_sector_organization_v1_pt');
Cache::forget('portal_home_sector_organization_v1_en');
Cache::forget('portal_home_territorial_v2_pt');
Cache::forget('portal_home_territorial_v2_en');
};
$model::saved($invalidate);
$model::deleted($invalidate);
+131
View File
@@ -6,10 +6,13 @@ use App\Models\LabourDemand;
use App\Models\TrainingCapacity;
use App\Models\User;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use JsonException;
final class EducationReviews
{
@@ -55,6 +58,134 @@ final class EducationReviews
});
}
/** Private preparation only; never a decision or an authority grant.
* @return array<string,mixed> */
public function preparation(User $actor, LabourDemand|TrainingCapacity $record): array
{
app(EducationAccess::class)->authorize($actor);
$event = $this->preparations($actor, $record)->orderByRaw("CASE WHEN (after_data->'education_preparation'->>'version') ~ '^[0-9]{1,10}$' THEN (after_data->'education_preparation'->>'version')::bigint END DESC NULLS LAST")->orderByDesc('occurred_at')->orderByDesc('id')->first(['after_data']);
$value = $event ? json_decode($event->after_data, true, 512, JSON_THROW_ON_ERROR)['education_preparation'] : null;
$snapshot = null;
$unavailable = false;
if ($value) {
try {
$decoded = json_decode(Crypt::decryptString($value['snapshot']), true, 512, JSON_THROW_ON_ERROR);
$snapshot = Validator::make(['snapshot' => $decoded], $this->preparationRules($decoded))->validate()['snapshot'];
} catch (DecryptException|JsonException|ValidationException) {
$unavailable = true;
}
}
// Empty reference maps remain JSON objects in the browser, like the declaration props.
if (is_array($snapshot)) {
if ($snapshot['base']['references'] === []) {
$snapshot['base']['references'] = (object) [];
}
if (is_array($snapshot['pending']) && $snapshot['pending']['base']['references'] === []) {
$snapshot['pending']['base']['references'] = (object) [];
}
}
return ['actor_id' => $actor->id, 'kind' => $record instanceof LabourDemand ? 'demands' : 'capacities', 'record_id' => $record->id, 'version' => (int) ($value['version'] ?? 0), 'operation_key' => $value['operation_key'] ?? null,
'snapshot' => $snapshot, 'unavailable' => $unavailable];
}
/** @param array<string,mixed> $input
* @return array<string,mixed> */
public function savePreparation(User $actor, string $kind, string $id, array $input): array
{
app(EducationAccess::class)->authorize($actor);
$model = match ($kind) {
'demands' => LabourDemand::class, 'capacities' => TrainingCapacity::class, default => abort(404)
};
$data = Validator::make($input, [...$this->preparationRules($input['snapshot'] ?? null),
'actor_id' => 'required|integer|min:1', 'version' => 'required|integer|min:0', 'operation_key' => 'required|uuid'])->validate();
abort_unless((int) $data['actor_id'] === $actor->id, 409, 'A conta mudou. Actualize a entrada antes de guardar a preparação.');
$key = strtolower($data['operation_key']);
$snapshot = $data['snapshot'];
foreach (['values', 'base'] as $field) {
$snapshot[$field]['version_no'] = (int) $snapshot[$field]['version_no'];
}
$snapshot['base']['quantity'] = (int) $snapshot['base']['quantity'];
foreach (['status', 'review_reason'] as $field) {
$snapshot['generation'][$field] = (int) $snapshot['generation'][$field];
}
if ($snapshot['pending'] !== null) {
$snapshot['pending']['values']['version_no'] = (int) $snapshot['pending']['values']['version_no'];
$snapshot['pending']['base']['version_no'] = (int) $snapshot['pending']['base']['version_no'];
$snapshot['pending']['base']['quantity'] = (int) $snapshot['pending']['base']['quantity'];
$snapshot['pending']['values']['client_key'] = strtolower($snapshot['pending']['values']['client_key']);
foreach (['reasonGeneration', 'statusGeneration'] as $field) {
$snapshot['pending'][$field] = (int) $snapshot['pending'][$field];
}
abort_unless($snapshot['pending']['values']['version_no'] === $snapshot['pending']['base']['version_no'], 422);
abort_unless($snapshot['pending']['reasonGeneration'] <= $snapshot['generation']['review_reason'] && $snapshot['pending']['statusGeneration'] <= $snapshot['generation']['status'], 422);
}
abort_unless($snapshot['values']['version_no'] === $snapshot['base']['version_no'], 422);
$hash = hash('sha256', json_encode(Arr::sortRecursive(['actor' => $actor->id, 'kind' => $kind, 'id' => $id,
'version' => (int) $data['version'], 'snapshot' => $snapshot]), JSON_THROW_ON_ERROR));
return DB::transaction(function () use ($actor, $model, $id, $data, $snapshot, $key, $hash): array {
$record = $model::lockForUpdate()->findOrFail($id);
$previous = $this->preparations($actor, $record)->whereRaw("after_data->'education_preparation'->>'operation_key' = ?", [$key])->first(['after_data']);
if ($previous) {
$receipt = json_decode($previous->after_data, true, 512, JSON_THROW_ON_ERROR)['education_preparation'];
abort_unless(hash_equals($receipt['hash'], $hash), 409, 'Esta tentativa de guardar já foi usada com outros dados.');
return $this->preparation($actor, $record);
}
$current = $this->preparation($actor, $record);
abort_unless($current['version'] === (int) $data['version'], 409, 'A preparação mudou noutra janela. Compare antes de substituir.');
abort_if($current['unavailable'], 409, 'A preparação anterior não pôde ser recuperada e foi preservada.');
abort_if($snapshot['base']['version_no'] > (int) $record->getAttribute('version_no'), 422, 'A versão analisada não existe nesta declaração.');
DB::table('audit_events')->insert(['source_system' => self::SOURCE, 'actor_user_id' => $actor->id,
'entity_type' => $record->getTable(), 'entity_id' => $id, 'action' => 'review_preparation_saved', 'data_classification' => 'restricted',
'after_data' => json_encode(['education_preparation' => ['version' => $current['version'] + 1, 'operation_key' => $key, 'hash' => $hash,
'snapshot' => Crypt::encryptString(json_encode($snapshot, JSON_THROW_ON_ERROR))]], JSON_THROW_ON_ERROR)]);
return $this->preparation($actor, $record);
});
}
private function preparations(User $actor, LabourDemand|TrainingCapacity $record): Builder
{
return DB::table('audit_events')->where('source_system', self::SOURCE)->where('actor_user_id', $actor->id)
->where('entity_type', $record->getTable())->where('entity_id', $record->id)->where('action', 'review_preparation_saved');
}
/** @return array<string,string> */
private function preparationRules(mixed $snapshot): array
{
$rules = ['snapshot' => 'required|array:values,generation,base,pending', 'snapshot.values' => 'required|array:version_no,status,review_reason',
'snapshot.generation' => 'required|array:status,review_reason', 'snapshot.pending' => 'present|nullable|array:values,reasonGeneration,statusGeneration,base'];
$roots = ['snapshot'];
if (is_array($snapshot) && is_array($snapshot['pending'] ?? null)) {
$roots[] = 'snapshot.pending';
$rules['snapshot.pending.values'] = 'required|array:version_no,status,review_reason,client_key';
$rules['snapshot.pending.values.client_key'] = 'required|uuid';
foreach (['reasonGeneration', 'statusGeneration'] as $field) {
$rules['snapshot.pending.'.$field] = 'required|integer|min:0|max:1000000000';
}
}
foreach (['status', 'review_reason'] as $field) {
$rules['snapshot.generation.'.$field] = 'required|integer|min:0|max:1000000000';
}
foreach ($roots as $root) {
$rules[$root.'.values.version_no'] = 'required|integer|min:1';
$rules[$root.'.values.status'] = 'required|in:reviewed,rejected';
$rules[$root.'.values.review_reason'] = 'present|string|max:3000';
$rules[$root.'.base'] = 'required|array:version_no,qualification,quantity,status,period_start,period_end,valid_until,source,reference_date,employer,references';
$rules[$root.'.base.version_no'] = 'required|integer|min:1';
$rules[$root.'.base.quantity'] = 'required|integer|min:0';
foreach (['qualification' => 250, 'status' => 40, 'period_start' => 40, 'period_end' => 40, 'valid_until' => 40, 'source' => 5000, 'reference_date' => 40, 'employer' => 1000] as $field => $max) {
$rules[$root.'.base.'.$field] = 'present|string|max:'.$max;
}
$rules[$root.'.base.references'] = 'present|array:occupation,skill,project,course,location';
$rules[$root.'.base.references.*'] = 'nullable|string|max:1000';
}
return $rules;
}
/** @return array{actor_id:int,commands:list<array{key:string,version:int}>} */
public function context(User $actor, LabourDemand|TrainingCapacity $record): array
{
@@ -0,0 +1,97 @@
<?php
namespace App\Services;
use App\Models\User;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
/** A selected public funding reference in the existing encrypted investment dossier. */
final class FundingDossierPreparation
{
/** @return array<string,mixed> */
public function context(User $actor, string $id, ?string $opportunity): array
{
$case = app(VirtualDesk::class)->case($actor, $id);
abort_unless(app(VirtualDesk::class)->owns($actor, $case) || $actor->can('manage-portal'), 403);
$state = app(InvestorContext::class)->state($case);
$scope = app(InvestmentConsumerScope::class);
$opportunity = $scope->key($state, $opportunity, true);
$specific = $scope->context($state, $opportunity);
$selections = app(VirtualDesk::class)->decode($case->payload)['investor_path']['funding_selections'] ?? [];
return ['id' => $id, 'version' => (int) $case->version, 'closed' => $case->closed_at !== null, 'opportunity' => $opportunity,
'territory' => $specific['selection']['territory']['id'] ?? null,
'selections' => array_values(array_filter($selections, fn ($row) => ($row['opportunity'] ?? null) === $opportunity)),
'href' => '/investimentos/'.$id.($opportunity ? '?opportunity='.urlencode($opportunity) : '').'#financiamento'];
}
/** @param array<string,mixed> $input
* @return array<string,mixed> */
public function save(User $actor, string $programme, array $input): array
{
$data = Validator::make($input, ['context' => 'required|uuid', 'opportunity' => 'nullable|string|max:150', 'version' => 'required|integer|min:1',
'version_id' => 'required|uuid', 'product' => 'nullable|string|max:80', 'need' => ['required', Rule::in(array_keys(FundingInstitutionDetails::NEEDS))],
'territory' => 'nullable|uuid', 'location_id' => 'nullable|string|max:100', 'location_mode' => 'nullable|in:physical,coverage', 'operation_key' => 'required|uuid', 'consent' => 'accepted'])->validate();
$intent = hash('sha256', json_encode(Arr::sortRecursive([...$data, 'actor' => $actor->id, 'programme' => $programme]), JSON_THROW_ON_ERROR));
return DB::transaction(function () use ($actor, $programme, $data, $intent): array {
$desk = app(VirtualDesk::class);
$case = $desk->case($actor, $data['context'], true, true);
abort_if($case->closed_at, 409, 'O dossier está encerrado.');
$context = $this->context($actor, $case->id, $data['opportunity'] ?? null);
$payload = $desk->decode($case->payload);
$selections = $payload['investor_path']['funding_selections'] ?? [];
foreach ($selections as $selection) {
if ($selection['id'] === $data['operation_key']) {
abort_unless(hash_equals($selection['intent'], $intent), 409, 'A intenção já tem outro conteúdo.');
return $context;
}
}
foreach ($selections as $selection) {
if (($selection['opportunity'] ?? null) === $context['opportunity'] && $selection['programme_id'] === $programme && $selection['version_id'] === $data['version_id'] && ($selection['product']['code'] ?? null) === ($data['product'] ?? null) && $selection['need'] === $data['need'] && ($selection['territory'] ?? null) === ($data['territory'] ?? null) && ($selection['location']['id'] ?? null) === ($data['location_id'] ?? null)) {
return $context;
}
}
abort_unless((int) $case->version === (int) $data['version'], 409, 'O dossier mudou. Consulte-o antes de guardar esta escolha.');
$catalogue = app(FundingProgrammes::class);
$record = $catalogue->published($programme);
abort_unless($record['version_id'] === $data['version_id'], 409, 'A ficha pública mudou. Confira a versão actual.');
if (! empty($data['territory'])) {
abort_unless(app(InvestmentGeography::class)->publicReferences()->where('id', $data['territory'])->exists(), 422);
}
$location = null;
if (! empty($data['location_id'])) {
foreach ($record['payload']['locations'] ?? [] as $candidate) {
if (($candidate['id'] ?? null) === $data['location_id']) {
$location = $candidate;
}
}
abort_unless($location, 422, 'O local seleccionado não pertence à versão consultada da instituição.');
}
$product = null;
if ($record['payload']['kind'] === 'institution') {
foreach ($record['payload']['products'] as $candidate) {
if (($candidate['code'] ?? null) === ($data['product'] ?? null)) {
$product = $candidate;
break;
}
}
abort_unless(($record['payload']['products'] === [] && empty($data['product']) && app(FundingInstitutionClassification::class)->permitsUnknownProduct($record['payload'], $data['need'])) || ($product && app(FundingInstitutionDetails::class)->match($product, $data)), 422, 'O serviço ou produto escolhido não corresponde à informação da ficha para esta necessidade/local. Confira as condições ou seleccione outra referência.');
}
abort_if(count($selections) >= 200, 422, 'O dossier atingiu o limite de referências de financiamento.');
$selection = ['id' => $data['operation_key'], 'intent' => $intent, 'actor' => $actor->id, 'opportunity' => $context['opportunity'],
'programme_id' => $programme, 'version_id' => $record['version_id'], 'version' => $record['version'], 'title' => $record['payload']['title'],
'record' => $record['payload'], 'product' => $product, 'need' => $data['need'], 'territory' => $data['territory'] ?? null, 'location' => $location, 'location_mode' => $data['location_mode'] ?? 'coverage', 'categories' => $record['payload']['institution_categories'] ?? [], 'institution_types' => $record['payload']['institution_types'] ?? [], 'institution_key' => $record['payload']['institution_key'] ?? null, 'recorded_at' => now()->toIso8601String(),
'notice' => 'Referência para preparação. Não é candidatura, contacto enviado, aprovação ou garantia de financiamento.'];
$payload['investor_path']['funding_selections'][] = $selection;
DB::table('desk_cases')->where('id', $case->id)->update(['payload' => $desk->encode($payload), 'version' => (int) $case->version + 1, 'updated_at' => now()]);
$desk->event($case, $actor, 'funding_preparation_saved', ['selection_id' => $selection['id'], 'programme_id' => $programme, 'version_id' => $record['version_id'], 'opportunity' => $context['opportunity']]);
return $this->context($actor, $case->id, $context['opportunity']);
});
}
}
@@ -0,0 +1,73 @@
<?php
namespace App\Services;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
/** Documentary classification and physical presence; neither is lending eligibility. */
final class FundingInstitutionClassification
{
public const CATEGORIES = ['banking' => 'Banca', 'credit' => 'Crédito e microfinanças', 'investment' => 'Gestão de investimento', 'pensions' => 'Fundos de pensões', 'insurance' => 'Seguros e resseguros', 'intermediation' => 'Mediação de seguros', 'exchange' => 'Câmbio', 'payments' => 'Pagamentos e serviços interbancários', 'supervision' => 'Supervisão', 'other' => 'Outras instituições'];
public const TYPES = ['commercial_bank' => 'Banco comercial', 'investment_bank' => 'Banco de investimento', 'bank' => 'Banco (tipo não especificado)', 'microbank' => 'Microbanco', 'cooperative' => 'Cooperativa de crédito', 'savings' => 'Organização de poupança e empréstimo', 'investment_manager' => 'Gestora de fundos de investimento', 'investment_company' => 'Sociedade de investimento', 'pension_manager' => 'Gestora de fundos de pensões', 'insurer' => 'Seguradora', 'reinsurer' => 'Resseguradora', 'microinsurer' => 'Micro-seguradora', 'broker' => 'Corretora de seguros', 'agent' => 'Agente de seguros', 'reinsurance_broker' => 'Corretor de resseguro', 'exchange_house' => 'Casa de câmbio', 'electronic_money' => 'Instituição de moeda electrónica', 'interbank' => 'Serviços interbancários', 'supervisor' => 'Supervisor', 'other' => 'Outro tipo documentado'];
/** @param array<string,mixed> $input
* @return array<string,mixed> */
public function normalize(array $input): array
{
return Validator::make($input, [
'institution_categories' => 'array|max:10', 'institution_categories.*' => ['required', 'distinct', Rule::in(array_keys(self::CATEGORIES))],
'institution_types' => 'array|max:10', 'institution_types.*' => ['required', 'distinct', Rule::in(array_keys(self::TYPES))],
'institution_services' => 'array|max:12', 'institution_services.*' => ['required', 'distinct', Rule::in(array_keys(FundingInstitutionDetails::NEEDS))],
'institution_status' => 'nullable|in:reported,historical,merged,unknown', 'successor_key' => 'nullable|string|max:100',
'register_reference' => 'nullable|string|max:2000', 'aliases' => 'array|max:100', 'aliases.*' => 'required|string|max:500',
'source_rows' => 'array|max:250', 'source_rows.*' => 'required|array:id,section,text', 'source_rows.*.id' => 'required|string|max:100',
'source_rows.*.section' => 'required|string|max:250', 'source_rows.*.text' => 'required|string|max:8000',
])->validate();
}
/** @param array<string,mixed> $filters */
public function filter(Builder $query, array $filters): void
{
foreach (['category' => 'institution_categories', 'institution_type' => 'institution_types'] as $key => $field) {
if (! empty($filters[$key])) {
$query->whereRaw('v.payload->? @> ?::jsonb', [$field, json_encode([$filters[$key]], JSON_THROW_ON_ERROR)]);
}
}
if (empty($filters['include_historical'])) {
$query->whereRaw("COALESCE(v.payload->>'institution_status','reported') NOT IN ('historical','merged')");
}
if (($filters['location_mode'] ?? 'coverage') !== 'physical') {
return;
}
$query->whereRaw("v.payload->>'kind'='institution'");
if (empty($filters['territory'])) {
return;
}
$unknown = ($filters['certainty'] ?? 'include_unknown') === 'include_unknown';
$path = array_column(app(InvestmentGeography::class)->path($filters['territory']), 'id');
$clauses = ["l->'territory_path' @> ?::jsonb"];
$bindings = [json_encode([['id' => $filters['territory']]], JSON_THROW_ON_ERROR)];
if ($unknown) {
foreach ($path as $id) {
$clauses[] = "l->>'territory_id' = ?";
$bindings[] = $id;
}
}
$status = $unknown ? '' : " AND COALESCE(l->>'evidence_status','reported') NOT IN ('to_confirm','historical','conflict','unlocated')";
$query->where(function (Builder $q) use ($clauses, $bindings, $status, $unknown): void {
$q->whereRaw("EXISTS (SELECT 1 FROM jsonb_array_elements(COALESCE(v.payload->'locations','[]'::jsonb)) l WHERE (".implode(' OR ', $clauses).')'.$status.')', $bindings);
if ($unknown) {
$q->orWhereRaw("NOT EXISTS (SELECT 1 FROM jsonb_array_elements(COALESCE(v.payload->'locations','[]'::jsonb)) l WHERE NULLIF(l->>'territory_id','') IS NOT NULL)");
}
});
}
/** @param array<string,mixed> $payload */
public function permitsUnknownProduct(array $payload, ?string $need): bool
{
return ! $need || ! isset($payload['institution_services']) || in_array($need, $payload['institution_services'], true);
}
}
+202
View File
@@ -0,0 +1,202 @@
<?php
namespace App\Services;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
/** Additional factual fields in the existing versioned funding catalogue. */
final class FundingInstitutionDetails
{
public const NATURES = ['bank' => 'Banco', 'credit' => 'Instituição de crédito', 'microfinance' => 'Microfinanças', 'investment_fund' => 'Fundo de investimento', 'development' => 'Agência de desenvolvimento/financiamento', 'other' => 'Outra natureza documentada', 'unknown' => 'Natureza por confirmar'];
public const NEEDS = ['investment' => 'Investimento/equipamento', 'working_capital' => 'Fundo de maneio', 'equity' => 'Participação de capital', 'guarantee' => 'Garantia', 'technical_assistance' => 'Assistência técnica', 'insurance' => 'Seguros', 'foreign_exchange' => 'Câmbio', 'payments' => 'Pagamentos', 'pension' => 'Pensões', 'investment_management' => 'Gestão de investimento', 'other' => 'Outra necessidade documentada'];
/** @param array<string,mixed> $input
* @return array<string,mixed> */
public function normalize(array $input, bool $publishing): array
{
$required = $publishing ? 'required' : 'nullable';
$rules = [
'institution_key' => 'required|string|regex:/^[a-z0-9][a-z0-9.-]{0,99}$/D',
'institution_nature' => ['required', Rule::in(array_keys(self::NATURES))],
'contacts' => 'array|max:100', 'locations' => 'array|max:100', 'products' => 'array|max:40',
'contacts.*' => 'array:id,kind,value,source_url,source_reference,checked_on,source_document_name,source_document_sha256,evidence_status,origin,source_refs',
'contacts.*.kind' => 'required|in:website,email,phone,address', 'contacts.*.value' => "$required|string|max:1000",
'locations.*' => 'array:id,role,label,address,territory_id,source_url,source_reference,checked_on,source_document_name,source_document_sha256,evidence_status,origin,source_refs,location_text,hours',
'locations.*.role' => 'required|in:headquarters,branch,contact_point', 'locations.*.label' => "$required|string|max:250",
'locations.*.address' => 'nullable|string|max:1500', 'locations.*.territory_id' => 'nullable|uuid',
'products.*' => 'array:code,title,instrument,needs,coverage,coverage_text,territory_ids,conditions,amounts,availability,contact_url,source_url,source_reference,checked_on,source_document_name,source_document_sha256',
'products.*.code' => 'required|string|regex:/^[a-z0-9][a-z0-9-]{0,79}$/D|distinct', 'products.*.title' => "$required|string|max:250",
'products.*.instrument' => 'nullable|string|max:250', 'products.*.needs' => 'array|max:6',
'products.*.needs.*' => ['required', 'distinct', Rule::in(array_keys(self::NEEDS))],
'products.*.coverage' => 'required|in:national,territorial,unknown', 'products.*.coverage_text' => 'nullable|string|max:2000',
'products.*.territory_ids' => 'array|max:30', 'products.*.territory_ids.*' => 'required|uuid|distinct',
'products.*.conditions' => 'nullable|string|max:4000', 'products.*.amounts' => 'nullable|string|max:2000',
'products.*.availability' => 'required|in:documented,unknown,closed', 'products.*.contact_url' => 'nullable|url:http,https|max:2000',
];
foreach (['contacts', 'locations', 'products'] as $prefix) {
$rules[$prefix.'.*.source_url'] = 'nullable|url:http,https|max:2000';
$rules[$prefix.'.*.source_document_name'] = 'nullable|string|max:250';
$rules[$prefix.'.*.source_document_sha256'] = 'nullable|string|regex:/^[a-f0-9]{64}$/D';
$rules[$prefix.'.*.source_reference'] = "$required|string|max:1500";
$rules[$prefix.'.*.checked_on'] = "$required|date_format:Y-m-d|before_or_equal:today";
}
foreach (['contacts', 'locations'] as $prefix) {
$rules[$prefix.'.*.id'] = 'nullable|string|max:100';
$rules[$prefix.'.*.evidence_status'] = 'nullable|in:reported,to_confirm,historical,conflict,unlocated';
$rules[$prefix.'.*.origin'] = 'nullable|string|max:500';
$rules[$prefix.'.*.source_refs'] = 'array|max:100';
$rules[$prefix.'.*.source_refs.*'] = 'required|string|max:100';
}
$rules['locations.*.location_text'] = 'nullable|string|max:1000';
$rules['locations.*.hours'] = 'nullable|string|max:1000';
$data = [...Validator::make($input, $rules)->validate(), ...app(FundingInstitutionClassification::class)->normalize($input)];
if ($publishing) {
foreach (['contacts', 'locations', 'products'] as $prefix) {
foreach ($data[$prefix] ?? [] as $item) {
Validator::make($item, ['source_url' => empty($item['source_document_sha256']) ? 'required|url:http,https' : 'nullable|url:http,https', 'source_document_name' => empty($item['source_url']) ? 'required|string' : 'nullable|string'])->validate();
}
}
}
foreach (['contacts', 'locations', 'products'] as $key) {
$data[$key] ??= [];
}
foreach ($data['contacts'] as $contact) {
Validator::make(['value' => $contact['value'] ?? null], ['value' => match ($contact['kind']) {
'website' => "$required|url:http,https|max:1000",
'email' => "$required|email|max:250",
default => "$required|string|max:1000",
}])->validate();
}
foreach ($data['locations'] as &$location) {
if (! empty($location['territory_id'])) {
$this->territory($location['territory_id']);
if ($publishing) {
$location['territory_path'] = $this->path($location['territory_id']);
}
}
}
unset($location);
foreach ($data['products'] as &$product) {
$product['needs'] ??= [];
$product['territory_ids'] ??= [];
Validator::make($product, ['territory_ids' => $product['coverage'] === 'territorial' ? ($publishing ? 'required|array|min:1' : 'array') : 'array|size:0'])->validate();
// Territorial drafts may be incomplete; other coverage modes never inherit the headquarters.
foreach ($product['territory_ids'] as $id) {
$this->territory($id);
}
if ($publishing) {
$product['territory_paths'] = array_map($this->path(...), $product['territory_ids']);
}
}
unset($product);
return $data;
}
private function territory(string $id): void
{
Validator::make(['territory' => $id], ['territory' => [Rule::in(app(InvestmentGeography::class)->publicReferences()->pluck('id')->all())]])->validate();
}
/** @return list<array{id:string,name:string,type:string}> */
private function path(string $id): array
{
return array_map(fn ($node) => ['id' => $node->id, 'name' => $node->name, 'type' => $node->location_type], app(InvestmentGeography::class)->path($id));
}
/** Same product must satisfy both location and need; branch addresses never imply lending coverage.
* @param array<string,mixed> $product
* @param array<string,mixed> $filters */
public function match(array $product, array $filters): bool
{
$unknown = ($filters['certainty'] ?? 'include_unknown') === 'include_unknown';
if ($product['availability'] === 'closed') {
return false;
}
if (! $unknown && $product['availability'] === 'unknown') {
return false;
}
if (! empty($filters['need']) && ! in_array($filters['need'], $product['needs'], true) && ! ($unknown && $product['needs'] === [])) {
return false;
}
if (empty($filters['territory'])) {
return true;
}
if ($product['coverage'] === 'national') {
return true;
}
if ($product['coverage'] === 'unknown') {
return $unknown;
}
$path = array_column(app(InvestmentGeography::class)->path($filters['territory']), 'id');
foreach ($product['territory_paths'] ?? [] as $covered) {
$ids = array_column($covered, 'id');
if (array_intersect($path, $product['territory_ids']) !== [] || in_array($filters['territory'], $ids, true)) {
return true;
}
}
return false;
}
/** @param array<string,mixed> $filters */
public function applyFilters(Builder $query, array $filters): void
{
app(FundingInstitutionClassification::class)->filter($query, $filters);
if (empty($filters['need']) && (empty($filters['territory']) || ($filters['location_mode'] ?? '') === 'physical')) {
return;
}
$unknown = ($filters['certainty'] ?? 'include_unknown') === 'include_unknown';
$conditions = ["p->>'availability' <> 'closed'"];
$bindings = [];
if (! $unknown) {
$conditions[] = "p->>'availability' <> 'unknown'";
}
if (! empty($filters['need'])) {
$conditions[] = "(p->'needs' @> ?::jsonb".($unknown ? " OR jsonb_array_length(p->'needs')=0" : '').')';
$bindings[] = json_encode([$filters['need']], JSON_THROW_ON_ERROR);
}
if (! empty($filters['territory']) && ($filters['location_mode'] ?? '') !== 'physical') {
$path = array_column(app(InvestmentGeography::class)->path($filters['territory']), 'id');
$parts = ["p->>'coverage'='national'", "EXISTS (SELECT 1 FROM jsonb_array_elements(COALESCE(p->'territory_paths','[]'::jsonb)) t WHERE t @> ?::jsonb)"];
$bindings[] = json_encode([['id' => $filters['territory']]], JSON_THROW_ON_ERROR);
foreach ($path as $id) {
$parts[] = "p->'territory_ids' @> ?::jsonb";
$bindings[] = json_encode([$id], JSON_THROW_ON_ERROR);
}
if ($unknown) {
$parts[] = "p->>'coverage'='unknown'";
}
$conditions[] = '('.implode(' OR ', $parts).')';
}
$sql = "EXISTS (SELECT 1 FROM jsonb_array_elements(COALESCE(v.payload->'products','[]'::jsonb)) p WHERE ".implode(' AND ', $conditions).')';
$query->where(function (Builder $q) use ($sql, $bindings, $unknown, $filters): void {
$q->where(function (Builder $q) use ($unknown, $filters): void {
$q->whereRaw("v.payload->>'kind' <> 'institution'");
if (! empty($filters['need'])) {
$q->where(function (Builder $q) use ($filters, $unknown): void {
$q->whereRaw("v.payload->'funding_needs' @> ?::jsonb", [json_encode([$filters['need']], JSON_THROW_ON_ERROR)]);
if ($unknown) {
$q->orWhereRaw("jsonb_array_length(COALESCE(v.payload->'funding_needs','[]'::jsonb))=0");
}
});
}
})->orWhere(function (Builder $q) use ($sql, $bindings, $unknown, $filters): void {
$q->whereRaw("v.payload->>'kind' = 'institution'")->where(function (Builder $q) use ($sql, $bindings, $unknown, $filters): void {
$q->whereRaw($sql, $bindings);
if ($unknown) {
$q->orWhere(function (Builder $p) use ($filters): void {
$p->whereRaw("jsonb_array_length(COALESCE(v.payload->'products','[]'::jsonb))=0");
if (! empty($filters['need'])) {
$p->where(fn ($z) => $z->whereRaw("v.payload->'institution_services' @> ?::jsonb", [json_encode([$filters['need']], JSON_THROW_ON_ERROR)])->orWhereRaw("NOT jsonb_exists(v.payload, 'institution_services')"));
}
});
}
});
});
});
}
}
+81 -7
View File
@@ -6,26 +6,100 @@ use App\Models\User;
use App\Support\CheckedFile;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
/** Packaged official references prepare private drafts through existing editorial authority. */
/** Packaged documentary references prepare private drafts through existing editorial authority. */
final class FundingProgrammeReferences
{
/** @return array{references:array<string,mixed>,sha256:string} */
private function package(): array
{
$references = [];
$hashes = [];
foreach (['funding-references.json', 'funding-institutions-nov2026.json'] as $file) {
$bytes = CheckedFile::read(database_path('data/'.$file));
$package = json_decode($bytes, true, 512, JSON_THROW_ON_ERROR);
abort_unless($package['format'] === 'investmoz-funding-references-v1', 503);
$references = [...$references, ...$package['references']];
$hashes[$file] = hash('sha256', $bytes);
}
return ['references' => $references, 'sha256' => hash('sha256', json_encode($hashes, JSON_THROW_ON_ERROR))];
}
/** @return array<string,mixed> */
public function preview(User $actor, string $code): array
{
app(FundingProgrammes::class)->guard($actor);
$bytes = CheckedFile::read(database_path('data/funding-references.json'));
$package = json_decode($bytes, true, 512, JSON_THROW_ON_ERROR);
abort_unless($package['format'] === 'investmoz-funding-references-v1', 503);
$package = $this->package();
$reference = $package['references'][$code] ?? null;
abort_unless(is_array($reference), 404);
$plan = ['code' => $code, 'edition' => $reference['edition'], 'draft' => $reference['draft'], 'provenance' => $reference['provenance'],
'package_sha256' => hash('sha256', $bytes)];
$unresolved = [];
$resolve = function (string $name) use (&$unresolved): ?string {
$matches = app(InvestmentGeography::class)->publicReferences()->where('location_type', 'province')->get(['id', 'name'])->filter(fn ($row) => Str::lower(Str::ascii($row->name)) === Str::lower(Str::ascii($name)));
if ($matches->count() !== 1) {
$unresolved[] = $name;
return null;
}
return $matches->first()->id;
};
if (! empty($reference['territory_names'])) {
$ids = array_values(array_filter(array_map($resolve, $reference['territory_names'])));
if (count($ids) === count($reference['territory_names'])) {
$reference['draft']['territory_ids'] = $ids;
} else {
$reference['draft']['coverage'] = 'unknown';
$reference['draft']['territory_ids'] = [];
$reference['draft']['coverage_text'] .= ' Correspondência ao cadastro territorial por confirmar.';
}
}
foreach ($reference['location_territory_names'] ?? [] as $index => $name) {
$reference['draft']['locations'][$index]['territory_id'] = $resolve($name);
if (! $reference['draft']['locations'][$index]['territory_id']) {
$reference['draft']['locations'][$index]['evidence_status'] = 'unlocated';
}
}
$plan = ['unresolved_territories' => $unresolved, 'code' => $code, 'edition' => $reference['edition'], 'draft' => $reference['draft'], 'provenance' => $reference['provenance'],
'package_sha256' => $package['sha256']];
$plan['hash'] = hash_hmac('sha256', json_encode(Arr::sortRecursive($plan), JSON_THROW_ON_ERROR), config('app.key'));
return $plan;
}
/** @return list<array{code:string,title:string}> */
public function choices(User $actor): array
{
app(FundingProgrammes::class)->guard($actor);
$package = $this->package();
$choices = [];
foreach ($package['references'] as $code => $reference) {
$choices[] = ['code' => $code, 'title' => $reference['draft']['title']];
}
return $choices;
}
/** @return array<string,mixed> */
public function assisted(User $actor): array
{
app(FundingProgrammes::class)->guard($actor);
$package = $this->package();
$existing = DB::table('funding_programmes')->get(['id', 'code', 'version', 'published_version_id'])->keyBy('code');
$items = [];
foreach ($package['references'] as $code => $reference) {
$draft = $reference['draft'];
$row = $existing->get($code);
$items[] = ['code' => $code, 'title' => $draft['title'], 'summary' => $draft['summary'], 'categories' => $draft['institution_categories'] ?? [],
'types' => $draft['institution_types'] ?? [], 'status' => $draft['institution_status'] ?? 'reported', 'source_document' => $draft['source_document_name'] ?? null,
'source_sha256' => $draft['source_document_sha256'] ?? null, 'locations' => $draft['locations'] ?? [], 'contacts' => $draft['contacts'] ?? [],
'id' => $row?->id, 'published' => $row?->published_version_id !== null];
}
return ['dataset_hash' => $package['sha256'], 'editor_id' => $actor->id, 'items' => $items];
}
/** @return array{id:string,created:bool} */
public function prepare(User $actor, string $code, string $hash): array
{
@@ -39,7 +113,7 @@ final class FundingProgrammeReferences
return ['id' => $existing->id, 'created' => false];
}
$id = app(FundingProgrammes::class)->save($actor, null, ['version' => 0, 'code' => $code,
'reason' => 'Preparação de referência oficial '.$plan['edition'].'; rascunho sem publicação automática.', 'draft' => $plan['draft']]);
'reason' => 'Preparação de referência documental '.$plan['edition'].'; rascunho sem publicação automática.', 'draft' => $plan['draft']]);
DB::table('audit_events')->insert(['source_system' => 'funding-catalogue', 'entity_type' => 'funding_programmes', 'entity_id' => $id,
'actor_user_id' => $actor->id, 'action' => 'reference_prepared', 'data_classification' => 'restricted',
'after_data' => json_encode(['edition' => $plan['edition'], 'package_sha256' => $plan['package_sha256'], 'provenance' => $plan['provenance'], 'effect' => 'private_draft_only'], JSON_THROW_ON_ERROR)]);
+41 -4
View File
@@ -18,7 +18,7 @@ use Symfony\Component\HttpFoundation\BinaryFileResponse;
/** Informational funding catalogue, separate from applications, credit decisions and fiscal calculations. */
final class FundingProgrammes
{
public const KINDS = ['fund' => 'Fundo', 'programme' => 'Programa de apoio', 'incentive' => 'Incentivo'];
public const KINDS = ['institution' => 'Instituição financeira', 'fund' => 'Fundo', 'programme' => 'Programa de apoio', 'incentive' => 'Incentivo'];
public const WINDOWS = ['dated' => 'Datas documentadas', 'rolling' => 'Recepção contínua documentada', 'unknown' => 'Janela por confirmar', 'closed' => 'Encerrada segundo a fonte'];
@@ -38,6 +38,19 @@ final class FundingProgrammes
abort_unless($this->access($actor), 403);
}
/** @param array<string,mixed> $reference */
public function matchesReference(User $actor, string $id, array $reference): bool
{
$this->guard($actor);
$row = DB::table('funding_programmes')->where('id', $id)->firstOrFail();
$expected = $this->normalize($actor, $reference, false);
$actual = json_decode($row->draft, true, 512, JSON_THROW_ON_ERROR);
abort_unless(is_array($actual), 503);
return json_encode(Arr::sortRecursive($actual), JSON_THROW_ON_ERROR)
=== json_encode(Arr::sortRecursive($expected), JSON_THROW_ON_ERROR);
}
public function publicQuery(): Builder
{
abort_unless($this->installed(), 404);
@@ -56,6 +69,7 @@ final class FundingProgrammes
public function catalogueQuery(array $filters): Builder
{
$query = $this->publicQuery();
app(FundingInstitutionDetails::class)->applyFilters($query, $filters);
$includeUnknown = ($filters['certainty'] ?? 'include_unknown') === 'include_unknown';
if (! empty($filters['sector'])) {
$query->where(function (Builder $scope) use ($filters, $includeUnknown): void {
@@ -66,10 +80,10 @@ final class FundingProgrammes
}
});
}
if (! empty($filters['territory'])) {
if (! empty($filters['territory']) && ($filters['location_mode'] ?? '') !== 'physical') {
$path = array_column(app(InvestmentGeography::class)->path($filters['territory']), 'id');
$query->where(function (Builder $scope) use ($filters, $path, $includeUnknown): void {
$scope->whereRaw("v.payload->>'coverage' = 'national'")
$scope->whereRaw("v.payload->>'kind' = 'institution'")->orWhereRaw("v.payload->>'coverage' = 'national'")
->orWhereJsonContains('v.payload->territory_ancestor_ids', $filters['territory']);
foreach ($path as $id) {
$scope->orWhereJsonContains('v.payload->territory_ids', $id);
@@ -102,6 +116,15 @@ final class FundingProgrammes
public function filterExplanation(array $record, array $filters): array
{
$payload = $record['payload'];
if ($payload['kind'] === 'institution') {
$products = array_values(array_filter($payload['products'] ?? [], fn ($p) => app(FundingInstitutionDetails::class)->match($p, $filters)));
if (($filters['location_mode'] ?? '') === 'physical') {
return ['institution' => 'Pesquisa por presença física documentada, não por cobertura de financiamento. Confira a precisão do local e as indicações por confirmar na ficha.'];
}
return ['institution' => $products === [] ? 'Instituição identificada; produtos, cobertura e disponibilidade por confirmar. Não é uma correspondência confirmada.' : 'Consulte os produtos correspondentes e as condições por confirmar. Sede e balcões não determinam a cobertura do financiamento.'];
}
$result = [];
if (! empty($filters['sector'])) {
$result['sector'] = $payload['sectors_scope'] === 'unknown'
@@ -200,6 +223,13 @@ final class FundingProgrammes
if (! $row && DB::table('funding_programmes')->where('code', $data['code'])->exists()) {
throw ValidationException::withMessages(['code' => 'Este identificador já pertence a uma ficha. Abra-a para actualizar ou acrescentar uma janela.']);
}
if ($draft['kind'] === 'institution') {
DB::select('select pg_advisory_xact_lock(261102, hashtext(?))', ['institution:'.$draft['institution_key']]);
$duplicate = DB::table('funding_programmes')->whereRaw("draft->>'kind' = 'institution'")->whereRaw("draft->>'institution_key' = ?", [$draft['institution_key']])->when($id, fn ($q) => $q->where('id', '<>', $id))->exists();
if ($duplicate) {
throw ValidationException::withMessages(['institution_key' => 'A instituição já tem ficha. Reutilize a identidade existente; não substitua as suas edições.']);
}
}
$id ??= (string) Str::uuid();
DB::table('funding_programmes')->updateOrInsert(['id' => $id], ['code' => $data['code'], 'version' => (int) $data['version'] + 1,
'draft' => json_encode($draft, JSON_THROW_ON_ERROR), 'created_by' => $row->created_by ?? $actor->id,
@@ -218,13 +248,14 @@ final class FundingProgrammes
$rules = [
'title' => "$required|string|max:250", 'kind' => ['required', Rule::in(array_keys(self::KINDS))], 'summary' => "$required|string|max:4000",
'manager_name' => 'nullable|string|max:250', 'manager_organization_id' => ['nullable', 'uuid', Rule::exists('organizations', 'id')->whereNull('deleted_at')],
'source_url' => "$required|url:http,https|max:2000", 'source_reference' => "$required|string|max:1500", 'source_checked_on' => "$required|date_format:Y-m-d|before_or_equal:today",
'source_url' => 'nullable|url:http,https|max:2000', 'source_document_name' => 'nullable|string|max:250', 'source_document_sha256' => 'nullable|string|regex:/^[a-f0-9]{64}$/D', 'source_reference' => "$required|string|max:1500", 'source_checked_on' => "$required|date_format:Y-m-d|before_or_equal:today",
'source_published_on' => 'nullable|date_format:Y-m-d', 'source_status' => 'required|in:identified,unknown,example',
'coverage' => 'required|in:national,territorial,unknown', 'coverage_text' => 'nullable|string|max:2000',
'territory_ids' => 'array|max:30', 'territory_ids.*' => 'required|uuid|distinct',
'sectors_scope' => 'required|in:all,selected,unknown', 'sectors_text' => 'nullable|string|max:2000', 'sector_ids' => 'array|max:40',
'sector_ids.*' => ['required', 'integer', 'distinct', Rule::in(Sector::choices()->whereNotNull('published_at')->pluck('id')->all())],
'resource_ids' => 'array|max:30', 'resource_ids.*' => 'required|integer|distinct|exists:portal_resources,id',
'funding_needs' => 'array|max:6', 'funding_needs.*' => ['required', 'distinct', Rule::in(array_keys(FundingInstitutionDetails::NEEDS))],
'criteria' => 'array|max:30', 'windows' => 'array|max:20',
'windows.*' => 'required|array:code,title,mode,start_on,end_on,coverage,coverage_text,territory_ids,source_url,source_reference,checked_on,application_url,criteria',
'windows.*.code' => 'required|string|regex:/^[a-z0-9][a-z0-9-]{0,79}$/D|distinct', 'windows.*.title' => "$required|string|max:250",
@@ -244,6 +275,12 @@ final class FundingProgrammes
$rules[$prefix.'.*.explanation'] = "$required|string|max:2000";
}
$data = Validator::make($input, $rules)->validate();
if ($publishing && empty($data['source_url'])) {
Validator::make($data, ['source_document_name' => 'required|string', 'source_document_sha256' => 'required|string|size:64'])->validate();
}
if ($data['kind'] === 'institution') {
$data = [...$data, ...app(FundingInstitutionDetails::class)->normalize($input, $publishing)];
}
$data = [...$data, 'territory_ids' => $data['territory_ids'] ?? [], 'sector_ids' => $data['sector_ids'] ?? [], 'resource_ids' => $data['resource_ids'] ?? [],
'criteria' => $data['criteria'] ?? [], 'windows' => $data['windows'] ?? []];
if ($data['coverage'] !== 'territorial' && $data['territory_ids'] !== []) {
+47
View File
@@ -3,6 +3,7 @@
namespace App\Services;
use App\Models\InvestmentDossier;
use App\Models\Opportunity;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\DB;
@@ -12,6 +13,52 @@ final class InvestmentGeography
{
public const FIELDS = ['province_id' => 'province', 'district_id' => 'district', 'admin_post_id' => 'admin_post', 'locality_id' => 'locality'];
/** Public declared coverage; a territory join never multiplies the opportunity total.
*
* @return array{opportunities: int, provinces: int}
*/
public function opportunityStatistics(): array
{
$opportunities = Opportunity::published()->select(['id', 'primary_territory_id', 'province', 'geography_scope'])->toBase();
$references = $this->publicReferences()->select(['id', 'parent_id', 'name', 'location_type']);
$result = DB::selectOne('WITH RECURSIVE
published_opportunities AS ('.$opportunities->toSql().'),
public_references AS ('.$references->toSql().'),
declared_locations AS (
SELECT id AS opportunity_id, primary_territory_id AS location_id
FROM published_opportunities WHERE primary_territory_id IS NOT NULL
UNION
SELECT l.opportunity_id, l.geo_location_id FROM opportunity_locations l
JOIN published_opportunities o ON o.id=l.opportunity_id
),
territory_paths AS (
SELECT d.opportunity_id, d.location_id, g.id, g.parent_id, g.name, g.location_type,
ARRAY[g.id] AS visited, 1 AS depth
FROM declared_locations d JOIN public_references g ON g.id=d.location_id
UNION ALL
SELECT t.opportunity_id, t.location_id, g.id, g.parent_id, g.name, g.location_type,
t.visited||g.id, t.depth+1
FROM territory_paths t JOIN public_references g ON g.id=t.parent_id
WHERE t.depth<5 AND NOT g.id=ANY(t.visited)
),
covered_provinces AS (
SELECT t.name FROM territory_paths t WHERE t.location_type=\'province\'
AND EXISTS (SELECT 1 FROM territory_paths root
WHERE root.opportunity_id=t.opportunity_id AND root.location_id=t.location_id AND root.parent_id IS NULL)
UNION
SELECT g.name FROM public_references g JOIN published_opportunities o
ON lower(btrim(o.province))=lower(btrim(g.name))
WHERE g.location_type=\'province\' AND o.primary_territory_id IS NULL
AND COALESCE(o.geography_scope, \'unspecified\')<>\'national\'
AND NOT EXISTS (SELECT 1 FROM opportunity_locations l WHERE l.opportunity_id=o.id)
)
SELECT (SELECT count(DISTINCT id) FROM published_opportunities) AS opportunities,
(SELECT count(DISTINCT lower(btrim(name))) FROM covered_provinces) AS provinces',
[...$opportunities->getBindings(), ...$references->getBindings()]);
return ['opportunities' => (int) $result->opportunities, 'provinces' => (int) $result->provinces];
}
/** Resolve an old province-name search only when the public register has exactly one match.
* @param array<array-key, mixed> $filters
* @return array<array-key, mixed>
@@ -0,0 +1,489 @@
<?php
namespace App\Services;
use App\Http\Controllers\InvestmentPortfolioController;
use App\Models\EconomicCluster;
use App\Models\User;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpKernel\Exception\HttpException;
/** Continuation of the existing private dossier, its documentary assessment and proactive evidence. */
final class InvestmentPreparationWorkspace
{
private const SOURCE = 'investor-preparation';
public function __construct(private readonly VirtualDesk $desk) {}
/** @return list<array<array-key,mixed>> */
private function rows(mixed $value): array
{
if (! is_array($value)) {
throw new \LogicException('Dossier list is not recoverable; original retained.');
}
$rows = [];
foreach ($value as $row) {
if (! is_array($row)) {
throw new \LogicException('Dossier row is not recoverable; original retained.');
} $rows[] = $row;
}
return $rows;
}
/** @param array<array-key,mixed> $data */
private function hash(array $data): string
{
return hash('sha256', json_encode(Arr::sortRecursive($data), JSON_THROW_ON_ERROR));
}
private function events(string $id, ?string $opportunity): Builder
{
return DB::table('audit_events')->where('source_system', self::SOURCE)->where('entity_type', 'desk_cases')->where('entity_id', $id)
->where('after_data->scope', $opportunity ?? 'dossier');
}
/** @return array<array-key,mixed> */
private function decode(?\stdClass $event): array
{
return $event ? json_decode(Crypt::decryptString(json_decode($event->after_data, true, 512, JSON_THROW_ON_ERROR)['snapshot']), true, 512, JSON_THROW_ON_ERROR)
: ['version' => 0, 'apiex' => ['additional_documents' => [], 'note' => '', 'review' => null, 'exports' => [], 'external' => []],
'swot' => ['object_key' => '', 'scope' => '', 'rows' => [], 'source_fingerprint' => '', 'review' => null, 'actions' => []]];
}
/** @return array<array-key,mixed> */
private function current(string $id, ?string $opportunity): array
{
return $this->decode($this->events($id, $opportunity)->orderByRaw("(after_data->>'version')::integer DESC")->first());
}
/** @return array{0: \stdClass, 1: array<array-key,mixed>, 2: ?string, 3: array<array-key,mixed>, 4: bool} */
private function context(User $actor, string $id, ?string $opportunity, bool $initialScope = false): array
{
abort_unless(app(InvestorPath::class)->available() && app(PrivateInvestmentPreparation::class)->available(), 404);
$case = $this->desk->case($actor, $id);
abort_unless($this->desk->owns($actor, $case, false) || $actor->can('manage-portal'), 403);
$context = app(InvestorContext::class)->state($case);
$scope = app(InvestmentConsumerScope::class);
$historical = false;
abort_if($initialScope && ! $this->events($id, null)->exists(), 404);
$choices = array_column(array_column($context['opportunity_choices'] ?? [], 'opportunity'), 'key');
if ((($opportunity && ! in_array($opportunity, $choices, true)) || ($initialScope && $choices)) && $this->events($id, $opportunity)->exists()) {
$saved = $this->current($id, $opportunity);
$last = $saved['apiex']['exports'] ? $saved['apiex']['exports'][array_key_last($saved['apiex']['exports'])] : null;
$oldContext = $saved['context_snapshot'] ?? $last['prepared']['context'] ?? $saved['swot']['sources_snapshot']['context'] ?? null;
abort_unless(is_array($oldContext), 409, 'Contexto histórico conservado; snapshot de consulta indisponível.');
$context = $oldContext;
$historical = true;
} else {
$opportunity = $scope->key($context, $opportunity, true);
$context = $scope->context($context, $opportunity);
}
$documents = app(InvestorContext::class)->preparationDocuments($id);
// Reuse request-level document authorization, including for a dossier team member.
$documents = array_values(array_filter($documents, function ($document) use ($actor, $id) {
$link = DB::table('desk_documents')->where('id', $document['id'])->where('case_id', $id)->first();
if ($link?->request_id) {
try {
$this->desk->request($actor, $link->request_id);
} catch (HttpException $e) {
if (in_array($e->getStatusCode(), [403, 404], true)) {
return false;
} throw $e;
}
}
return true;
}));
foreach ($documents as &$document) {
$record = DB::table('documents')->where('id', $document['document_id'])->first();
$document['available'] = $record && $record->status === 'active' && $record->malware_scan_status === 'clean'
&& hash_equals(trim($record->sha256), trim((string) $record->malware_scanned_sha256));
}
unset($document);
$unique = [];
foreach ($documents as $document) {
$unique[$document['document_id']] = $document;
}
return [$case, $context, $opportunity, array_values($unique), $historical];
}
/** @param array<array-key,mixed> $context
* @return array<array-key,mixed> */
private function sources(User $actor, \stdClass $case, array $context, string $object): array
{
$objects = [['key' => 'investment:'.$case->id, 'label' => $case->title, 'kind' => 'investment']];
$evidence = [];
$refs = array_values($context['selection'] ?? []);
foreach ($context['opportunity_choices'] ?? [] as $choice) {
$refs[] = $choice['opportunity'];
}
foreach ($refs as $ref) {
if (! is_array($ref) || empty($ref['key'])) {
continue;
}
try {
$live = app(InvestorContext::class)->reference($ref['key']);
} catch (ModelNotFoundException|HttpException $e) {
$live = ['key' => $ref['key'], 'unavailable' => true];
}
$evidence[$ref['key']] = ['key' => $ref['key'], 'label' => $ref['title'] ?? $ref['key'], 'consulted' => $ref, 'current' => $live];
if (in_array($ref['kind'] ?? '', ['territory', 'sector'], true)) {
$objects[] = ['key' => $ref['key'], 'label' => $ref['title'], 'kind' => $ref['kind']];
}
if (($ref['kind'] ?? '') === 'portfolio') {
$portfolio = InvestmentPortfolioController::visible()->whereKey($ref['id'])->first();
if ($portfolio?->cluster_id) {
$cluster = EconomicCluster::find($portfolio->cluster_id);
if ($cluster) {
$objects[] = ['key' => 'cluster:'.$cluster->id, 'label' => $cluster->getAttribute('name'), 'kind' => 'cluster'];
}
}
}
}
// Sector/territory may have been deliberately entered rather than selected as a source.
foreach ([['sector', 'sector_id'], ['territory', 'province_id'], ['territory', 'district_id'], ['territory', 'admin_post_id'], ['territory', 'locality_id']] as [$kind, $field]) {
if (! empty($context['fields'][$field])) {
$key = $kind.':'.$context['fields'][$field];
try {
$ref = app(InvestorContext::class)->reference($key);
$objects[] = ['key' => $key, 'label' => $ref['title'], 'kind' => $kind];
$evidence[$key] = ['key' => $key, 'label' => $ref['title'], 'consulted' => $ref, 'current' => $ref];
} catch (ModelNotFoundException|HttpException $e) { /* Keep the declared context; do not invent evidence. */
}
}
}
// A manually chosen cluster must already be a public, visible portfolio association.
if (str_starts_with($object, 'cluster:')) {
$clusterId = substr($object, 8);
$visible = InvestmentPortfolioController::visible()->where('cluster_id', $clusterId)->exists();
$cluster = $visible ? EconomicCluster::find($clusterId) : null;
$objects[] = ['key' => $object, 'label' => $cluster ? $cluster->getAttribute('name') : 'Cluster da análise guardada — fonte actual indisponível', 'kind' => 'cluster'];
}
foreach ($objects as $item) {
if ($item['kind'] === 'cluster') {
$evidence[$item['key']] = ['key' => $item['key'], 'label' => 'Competitividade — '.$item['label'],
'current' => InvestmentPortfolioController::visible()->where('cluster_id', substr($item['key'], 8))->exists() ? app(ClusterCompetitiveness::class)->publicData(substr($item['key'], 8)) : null, 'notice' => 'Versão pública aprovada; ausência de análise não prova ausência de oferta/concorrência.'];
}
}
$unique = [];
foreach ($objects as $item) {
$unique[$item['key']] = $item;
}
return [array_values($unique), array_values($evidence)];
}
/** @return array<array-key,mixed> */
public function view(User $actor, string $id, ?string $opportunity = null, ?int $historyBefore = null, bool $initialScope = false): array
{
[$case, $context, $opportunity, $documents, $historical] = $this->context($actor, $id, $opportunity, $initialScope);
$state = $this->current($id, $opportunity);
[$objects, $sources] = $this->sources($actor, $case, $context, $state['swot']['object_key']);
$payload = $this->desk->decode($case->payload);
$reused = ['investor' => Arr::only($payload, ['name', 'email']), 'profile' => Arr::only($payload['profile'] ?? [], ['phone', 'nuit', 'address', 'country_code', 'nationality', 'residence_country_code']), 'company' => Arr::only($payload['company'] ?? [], ['id', 'name', 'nuit', 'registration', 'address', 'type']), 'company_version' => $payload['company_version'] ?? null, 'notice' => 'Valores autorizados conservados no dossier; não foram substituídos por dados recentes de outra origem.'];
if ($historical && isset($state['reused_snapshot'])) {
$reused = $state['reused_snapshot'];
}
foreach ($documents as $document) {
$sources[] = ['key' => 'document:'.$document['document_id'], 'label' => $document['file_name'], 'current' => $document, 'notice' => 'Documento privado do dossier; disponibilidade não certifica o conteúdo.'];
}
$assessment = app(PrivateInvestmentPreparation::class)->assess($context, $documents);
$issues = $assessment['missing_labels'];
if (! $assessment['enabled'] || ! $assessment['period_covered'] || ! $assessment['scope_covered'] || $assessment['mode'] === 'indeterminate') {
$issues[] = 'Completar o enquadramento da preparação existente.';
}
foreach ($assessment['requirements'] as &$requirement) {
$document = collect($this->rows($documents))->firstWhere('document_id', $requirement['document_id']);
$requirement['available'] = $document['available'] ?? false;
if ($requirement['state'] === 'indeterminate' || ($requirement['required'] && ! $requirement['available'])) {
$issues[] = $requirement['label'];
}
}
unset($requirement);
$selected = array_values(array_unique([...collect($this->rows($assessment['requirements']))->where('required', true)->pluck('document_id')->filter()->all(), ...$state['apiex']['additional_documents']]));
foreach ($selected as $doc) {
if (! (collect($this->rows($documents))->firstWhere('document_id', $doc)['available'] ?? false)) {
$issues[] = 'Anexo indisponível: '.$doc;
}
}
$documentary = array_values(array_filter($documents, fn ($d) => in_array($d['document_id'], $selected, true)));
$fingerprint = $this->hash(['context' => $context, 'reused' => $reused, 'assessment' => $assessment, 'documents' => $documentary, 'note' => $state['apiex']['note']]);
$swotFingerprint = $this->hash($this->sourceSnapshot($context, $reused, $sources, $state['swot']['object_key'], $state['swot']['rows']));
$external = collect($this->rows($state['apiex']['external']));
$apiState = $external->contains('kind', 'decision') ? 'decision_received' : ($external->contains('kind', 'delivery') ? 'delivery_recorded' : (! $issues && ($state['apiex']['review']['fingerprint'] ?? null) === $fingerprint ? 'ready' : 'preparing'));
$query = $opportunity ? '?opportunity='.urlencode($opportunity) : '';
$editQuery = $historical ? '' : $query;
$savedScopes = DB::table('audit_events')->where('source_system', self::SOURCE)->where('entity_type', 'desk_cases')->where('entity_id', $id)->selectRaw("DISTINCT after_data->>'scope' as scope")->get()->map(fn ($row) => ['key' => $row->scope, 'label' => $row->scope === 'dossier' ? 'Contexto inicial do dossier' : $row->scope, 'href' => '/investimentos/'.$id.'/preparacao?'.($row->scope === 'dossier' ? 'history_scope=dossier' : 'opportunity='.urlencode($row->scope))])->all();
$historyRows = $this->events($id, $opportunity)->when($historyBefore !== null, fn ($q) => $q->whereRaw("(after_data->>'version')::integer < ?", [$historyBefore]))->orderByRaw("(after_data->>'version')::integer DESC")->limit(26)->get();
$history = $historyRows->take(25)->map(fn ($e) => ['version' => json_decode($e->after_data, true)['version'], 'action' => $e->action, 'at' => $e->occurred_at, 'actor' => $e->actor_user_id, 'operation_key' => $e->correlation_id])->values()->all();
return ['case' => ['id' => $id, 'title' => $case->title, 'closed' => $case->closed_at !== null], 'opportunity' => $opportunity, 'opportunity_label' => $context['selection']['opportunity']['title'] ?? null,
'historical' => $historical, 'initial_scope' => $initialScope, 'saved_scopes' => $savedScopes, 'version' => $state['version'], 'apiex' => $state['apiex'], 'swot' => $state['swot'], 'assessment' => $assessment, 'context' => $context, 'reused' => $reused,
'documents' => $documents, 'selected_documents' => $documentary, 'api_issues' => array_values(array_unique($issues)), 'api_state' => $apiState,
'api_fingerprint' => $fingerprint, 'swot_fingerprint' => $swotFingerprint,
'swot_review_required' => $state['swot']['source_fingerprint'] !== $swotFingerprint || ($state['swot']['review']['fingerprint'] ?? null) !== $swotFingerprint,
'objects' => $objects, 'sources' => $sources, 'can_write' => ! $historical && ! $case->closed_at && $this->desk->owns($actor, $case),
'can_review' => ! $historical && ! $case->closed_at && ($this->desk->owns($actor, $case) || $actor->can('manage-portal')),
'links' => ['dossier' => '/investimentos/'.$id.$editQuery, 'context' => '/investimentos/'.$id.'/contexto/editar'.$editQuery,
'documents' => '/balcao-virtual/dossiers/'.$id.$query, 'partners' => '/parcerias/painel?context='.$id.($opportunity ? '&opportunity='.urlencode($opportunity) : ''),
'library' => '/documentos?context='.$id.($opportunity ? '&opportunity='.urlencode($opportunity) : ''),
'editorial' => '/acesso-gestao', 'idea' => '/investimentos/'.$id.'/contexto/editar'.$query.'#project-summary', 'opportunity' => '/investimentos/'.$id.'/contexto/editar'.$query.'#project-summary', 'risk' => '/investimentos/'.$id.$query.'#tarefas'],
'history' => $history, 'history_next' => $historyRows->count() > 25 ? ($history[count($history) - 1]['version'] ?? null) : null];
}
/** @param array<array-key,mixed> $context
* @param array<array-key,mixed> $reused
* @param array<array-key,mixed> $sources
* @param array<array-key,mixed> $rows
* @return array<array-key,mixed> */
private function sourceSnapshot(array $context, array $reused, array $sources, string $object, array $rows): array
{
$used = array_column($rows, 'source_key');
$tracked = array_values(array_filter($sources, fn ($source) => ! str_starts_with($source['key'], 'document:') || in_array($source['key'], $used, true)));
return ['context' => $context, 'reused' => $reused, 'sources' => $tracked, 'object' => $object];
}
/** @return array<array-key,mixed>|null */
public function continuation(User $actor, string $id, ?string $opportunity, mixed $actionId): ?array
{
if (! is_string($actionId) || ! Str::isUuid($actionId)) {
return null;
}
[, , $scope] = $this->context($actor, $id, $opportunity);
return collect($this->rows($this->current($id, $scope)['swot']['actions']))->firstWhere('id', $actionId);
}
/** @return list<string> */
public function taskKeys(User $actor, string $id, string $opportunity): array
{
[, , $scope] = $this->context($actor, $id, $opportunity);
return array_values(array_map(fn ($action) => 'swot:'.$action['id'], $this->current($id, $scope)['swot']['actions']));
}
/** @param array<array-key,mixed> $input
* @param array<array-key,mixed> $view
* @return array<array-key,mixed> */
private function draft(array $input, array $view): array
{
$data = Validator::make($input, ['object_key' => ['required', Rule::in(array_column($view['objects'], 'key'))], 'scope' => 'present|nullable|string|max:4000',
'source_fingerprint' => 'present|string|size:64', 'rows' => 'present|array|list|max:80', 'rows.*' => 'array:id,dimension,text,reference,source_key,date,classification',
'rows.*.id' => 'required|uuid|distinct', 'rows.*.dimension' => 'required|in:strength,weakness,opportunity,threat',
'rows.*.classification' => 'required|in:documented_fact,hypothesis,unconfirmed', 'rows.*.text' => 'present|nullable|string|max:4000',
'rows.*.reference' => 'present|nullable|string|max:4000', 'rows.*.source_key' => ['nullable', Rule::in(array_column($view['sources'], 'key'))], 'rows.*.date' => 'nullable|date_format:Y-m-d'])->validate();
$snapshot = $this->sourceSnapshot($view['context'], $view['reused'], $view['sources'], $data['object_key'], $data['rows']);
return [...$data, 'sources_snapshot' => $snapshot, 'snapshot_fingerprint' => $this->hash($snapshot), 'review' => null, 'actions' => $view['swot']['actions']];
}
/** @param array<array-key,mixed> $input
* @return array<array-key,mixed> */
public function change(User $actor, string $id, array $input): array
{
$control = Validator::make($input, ['opportunity' => 'nullable|string|max:150', 'version' => 'required|integer|min:0', 'operation_key' => 'required|uuid',
'action' => 'required|in:apiex_save,apiex_review,apiex_export,external,swot_save,swot_review,swot_action,swot_restore', 'reason' => 'required|string|min:3|max:4000'])->validate();
return DB::transaction(function () use ($actor, $id, $input, $control) {
$case = $this->desk->case($actor, $id, false, true);
$this->desk->assertOpen($case);
$view = $this->view($actor, $id, $control['opportunity'] ?? null);
$review = in_array($control['action'], ['apiex_review', 'swot_review'], true);
abort_unless($review ? $view['can_review'] : $view['can_write'], 403);
$requestHash = $this->hash([...$input, 'actor' => $actor->id]);
$prior = $this->events($id, $view['opportunity'])->where('correlation_id', $control['operation_key'])->first();
if ($prior) {
abort_unless(hash_equals(json_decode($prior->after_data, true)['request_hash'], $requestHash), 409, 'A tentativa já tem outro conteúdo.');
return [...$this->view($actor, $id, $view['opportunity']), 'replayed' => true];
}
abort_unless((int) $control['version'] === $view['version'], 409, 'Existe outra versão. Os seus campos foram preservados; consulte e compare antes de substituir.');
$state = Arr::only($view, ['version', 'apiex', 'swot']);
$receipt = ['actor' => $actor->id, 'at' => now()->toISOString(), 'reason' => $control['reason']];
switch ($control['action']) {
case 'apiex_save':
$data = Validator::make($input, ['note' => 'present|nullable|string|max:6000', 'additional_documents' => 'present|array|list|max:60', 'additional_documents.*' => ['required', 'distinct', Rule::in(array_column($view['documents'], 'document_id'))]])->validate();
$state['apiex'] = [...$state['apiex'], ...$data, 'review' => null];
break;
case 'apiex_review':
abort_if($view['api_issues'], 422, 'Complete os requisitos e documentos aplicáveis.');
abort_unless(($input['fingerprint'] ?? '') === $view['api_fingerprint'], 409, 'O dossier/documentos mudaram; reveja a versão actual.');
foreach ($view['selected_documents'] as $document) {
$record = DB::table('documents')->where('id', $document['document_id'])->whereNull('deleted_at')->lockForUpdate()->first();
abort_unless($record !== null, 404);
app(VerifiedDocumentBinary::class)->respond($record);
}
$state['apiex']['review'] = [...$receipt, 'fingerprint' => $view['api_fingerprint']];
break;
case 'apiex_export':
abort_if($view['api_issues'] || ($state['apiex']['review']['fingerprint'] ?? '') !== $view['api_fingerprint'], 409, 'Reveja a preparação actual antes de exportar.');
$prepared = Arr::only($view, ['case', 'opportunity', 'context', 'reused', 'assessment', 'selected_documents']);
$prepared['apiex'] = Arr::only($state['apiex'], ['note', 'review']);
$state['apiex']['exports'][] = [...$receipt, 'id' => $control['operation_key'], 'fingerprint' => $view['api_fingerprint'], 'context_revision' => $view['context']['revision'], 'documents' => $view['selected_documents'], 'prepared' => $prepared];
break;
case 'external':
$data = Validator::make($input, ['kind' => 'required|in:delivery,clarification,response,decision', 'date' => 'required|date_format:Y-m-d|before_or_equal:today', 'reference' => 'required|string|max:2000',
'proof_document' => ['required', Rule::in(collect($this->rows($view['documents']))->where('available', true)->pluck('document_id')->all())], 'export_id' => 'required|uuid', 'related_id' => 'nullable|uuid', 'outcome' => 'nullable|in:approved,refused,conditional,other'])->validate();
abort_unless(collect($this->rows($state['apiex']['exports']))->contains('id', $data['export_id']), 422, 'Escolha a versão exportada deste dossier/oportunidade.');
$same = collect($this->rows($state['apiex']['external']))->where('export_id', $data['export_id']);
if ($data['kind'] !== 'delivery') {
abort_unless($same->contains('kind', 'delivery'), 422, 'Registe a entrega externa desta versão.');
}
if ($data['kind'] === 'response') {
abort_unless($same->where('kind', 'clarification')->contains('id', $data['related_id'] ?? ''), 422, 'Identifique o pedido de esclarecimento a que responde.');
}
if ($data['kind'] === 'decision') {
abort_unless(! empty($data['outcome']), 422, 'Identifique a decisão efectivamente recebida.');
}
$record = DB::table('documents')->where('id', $data['proof_document'])->whereNull('deleted_at')->lockForUpdate()->first();
abort_unless($record !== null, 404);
app(VerifiedDocumentBinary::class)->respond($record);
$state['apiex']['external'][] = [...$data, ...$receipt, 'id' => $control['operation_key'], 'proof_snapshot' => collect($this->rows($view['documents']))->firstWhere('document_id', $data['proof_document'])];
break;
case 'swot_save':
$state['swot'] = $this->draft($input['draft'] ?? [], $view);
break;
case 'swot_review':
$swot = $state['swot'];
abort_unless(($input['fingerprint'] ?? '') === $view['swot_fingerprint'], 409, 'As fontes mudaram; compare antes de rever.');
abort_unless(trim($swot['scope'] ?? '') !== '' && in_array($swot['object_key'], array_column($view['objects'], 'key'), true), 422, 'Identifique objecto e âmbito.');
foreach (['strength', 'weakness', 'opportunity', 'threat'] as $dimension) {
abort_unless(collect($this->rows($swot['rows']))->contains('dimension', $dimension), 422, 'Trate as quatro dimensões; incerteza deve ficar classificada.');
}
foreach ($swot['rows'] as $row) {
abort_unless(trim($row['text'] ?? '') !== '' && trim($row['reference'] ?? '') !== '' && ! empty($row['date']), 422, 'Cada conclusão exige fonte, data e classificação; desconhecido não é ausência.');
}
$state['swot']['source_fingerprint'] = $view['swot_fingerprint'];
$state['swot']['sources_snapshot'] = $this->sourceSnapshot($view['context'], $view['reused'], $view['sources'], $state['swot']['object_key'], $state['swot']['rows']);
$state['swot']['snapshot_fingerprint'] = $view['swot_fingerprint'];
$state['swot']['review'] = [...$receipt, 'fingerprint' => $view['swot_fingerprint'], 'version' => $state['version'] + 1];
break;
case 'swot_action':
abort_if($view['swot_review_required'], 409, 'Reveja a análise e as fontes antes de ligar uma acção.');
$data = Validator::make($input, ['conclusion_id' => ['required', Rule::in(array_column($state['swot']['rows'], 'id'))], 'kind' => 'required|in:idea,opportunity,partners,risk', 'title' => 'required|string|min:3|max:200'])->validate();
$href = $view['links'][$data['kind']];
if ($data['kind'] === 'risk') {
$href = str_replace('#tarefas', (str_contains($href, '?') ? '&' : '?').'swot_action='.$control['operation_key'].'#tarefas', $href);
}
$state['swot']['actions'][] = [...$data, ...$receipt, 'id' => $control['operation_key'], 'case_id' => $id, 'opportunity' => $view['opportunity'], 'review_version' => $state['swot']['review']['version'], 'href' => $href];
break;
case 'swot_restore':
$data = Validator::make($input, ['revision' => 'required|integer|min:1'])->validate();
$event = $this->events($id, $view['opportunity'])->where('after_data->version', $data['revision'])->first();
abort_unless($event !== null, 404);
$old = $this->decode($event)['swot'];
$state['swot'] = [...$old, 'review' => null, 'actions' => $state['swot']['actions']];
break;
}
$state['context_snapshot'] = $view['context'];
$state['reused_snapshot'] = $view['reused'];
$state['version']++;
DB::table('audit_events')->insert(['source_system' => self::SOURCE, 'entity_type' => 'desk_cases', 'entity_id' => $id, 'external_id' => $id.':'.hash('sha256', $view['opportunity'] ?? 'dossier').':'.$state['version'],
'correlation_id' => $control['operation_key'], 'actor_user_id' => $actor->id, 'action' => $control['action'], 'data_classification' => 'restricted',
'before_data' => json_encode(['version' => $view['version']], JSON_THROW_ON_ERROR), 'after_data' => json_encode(['version' => $state['version'], 'scope' => $view['opportunity'] ?? 'dossier', 'request_hash' => $requestHash, 'snapshot' => Crypt::encryptString(json_encode($state, JSON_THROW_ON_ERROR))], JSON_THROW_ON_ERROR)]);
return [...$this->view($actor, $id, $view['opportunity']), 'replayed' => false];
}, 3);
}
/** Export uses the existing verified binary reader, never a browser-supplied path.
* @return array{bytes:string,export_id:string} */
public function bundle(User $actor, string $id, ?string $opportunity, string $exportId, bool $initialScope = false): array
{
return DB::transaction(function () use ($actor, $id, $opportunity, $exportId, $initialScope) {
$this->desk->case($actor, $id, false, true);
if ($opportunity === null && ! $initialScope) {
$scopes = DB::table('audit_events')->where('source_system', self::SOURCE)->where('entity_type', 'desk_cases')->where('entity_id', $id)->where('correlation_id', $exportId)->where('action', 'apiex_export')->selectRaw("DISTINCT after_data->>'scope' as scope")->get();
abort_unless($scopes->count() === 1, 422, 'Identifique o âmbito da exportação preservada.');
$scope = $scopes->first()->scope;
$initialScope = $scope === 'dossier';
$opportunity = $initialScope ? null : $scope;
}
$view = $this->view($actor, $id, $opportunity, null, $initialScope);
$export = collect($this->rows($view['apiex']['exports']))->firstWhere('id', $exportId);
abort_unless($export !== null, 404);
$records = [];
foreach ($export['documents'] as $document) {
abort_unless(collect($this->rows($view['documents']))->contains('document_id', $document['document_id']), 403);
$record = DB::table('documents')->where('id', $document['document_id'])->whereNull('deleted_at')->lockForUpdate()->first();
abort_unless($record !== null && $record->status === 'active' && $record->malware_scan_status === 'clean' && hash_equals(trim($document['sha256']), trim($record->sha256)) && hash_equals(trim($record->sha256), trim((string) $record->malware_scanned_sha256)), 404, 'Documento da versão preparada indisponível para descarga; o histórico foi preservado.');
app(VerifiedDocumentBinary::class)->authorizeOpportunityAttachment($record);
$records[$document['document_id']] = $record;
}
$disk = Storage::disk('local');
$key = 'investor-preparations/'.$id.'/'.hash('sha256', $view['opportunity'] ?? 'dossier').'/'.$exportId.'.zip.enc';
if ($disk->exists($key)) {
try {
$sealed = json_decode(Crypt::decryptString($disk->get($key)), true, 512, JSON_THROW_ON_ERROR);
} catch (DecryptException|\JsonException $e) {
abort(404, 'Pacote preservado indisponível; não foi substituído.');
}
abort_unless(($sealed['case_id'] ?? '') === $id && ($sealed['opportunity'] ?? null) === $view['opportunity'] && ($sealed['export_id'] ?? '') === $exportId && ($sealed['fingerprint'] ?? '') === $export['fingerprint'], 404);
$bytes = base64_decode($sealed['binary'] ?? '', true);
abort_unless(is_string($bytes) && hash_equals($sealed['sha256'] ?? '', hash('sha256', $bytes)), 404);
return ['bytes' => $bytes, 'export_id' => $exportId];
}
$prepared = $export['prepared'];
abort_unless(is_array($prepared), 409, 'Snapshot de exportação indisponível; original conservado.');
$file = tempnam(sys_get_temp_dir(), 'investmoz-apiex-');
abort_unless(is_string($file), 503);
$zip = new \ZipArchive;
$open = false;
try {
abort_unless($zip->open($file, \ZipArchive::OVERWRITE) === true, 503);
$open = true;
$manifest = ['format' => 'investmoz-apiex-preparation/1', 'case_id' => $id, 'opportunity' => $view['opportunity'], 'export' => $export, 'context' => $prepared['context'], 'reused' => $prepared['reused'], 'assessment' => $prepared['assessment'], 'notice' => 'Preparação para entrega externa. Não é formulário oficial, submissão electrónica ou aprovação.'];
$index = view('investments.preparation-index', ['workspace' => $prepared, 'manifest' => $manifest])->render();
abort_unless($zip->addFromString('indice.html', $index), 503);
abort_unless($zip->addFromString('manifesto.json', json_encode($manifest, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)), 503);
foreach ($export['documents'] as $n => $document) {
$record = $records[$document['document_id']];
$response = app(VerifiedDocumentBinary::class)->respond($record);
$bytes = $response instanceof BinaryFileResponse ? file_get_contents($response->getFile()->getPathname()) : $response->getContent();
abort_unless(is_string($bytes), 503);
abort_unless($zip->addFromString('anexos/'.sprintf('%02d-', $n + 1).preg_replace('/[^\pL\pN._-]+/u', '-', basename($document['file_name'])), $bytes), 503);
}
abort_unless($zip->close(), 503);
$open = false;
$bytes = file_get_contents($file);
abort_unless(is_string($bytes), 503);
$sealed = ['case_id' => $id, 'opportunity' => $view['opportunity'], 'export_id' => $exportId, 'fingerprint' => $export['fingerprint'], 'sha256' => hash('sha256', $bytes), 'binary' => base64_encode($bytes)];
$temporary = $key.'.'.Str::uuid().'.tmp';
try {
abort_unless($disk->put($temporary, Crypt::encryptString(json_encode($sealed, JSON_THROW_ON_ERROR))) && $disk->move($temporary, $key), 503, 'Não foi possível preservar o pacote; repita a mesma exportação.');
} finally {
$disk->delete($temporary);
}
return ['bytes' => $bytes, 'export_id' => $exportId];
} finally {
if ($open) {
$zip->close();
}
if (is_file($file)) {
unlink($file);
}
}
});
}
}
+13 -5
View File
@@ -160,7 +160,9 @@ final class InvestorPath
$next = ['code' => 'S06', 'title' => $preparation['next'], 'action' => 'Preparar', 'href' => '/investimentos/'.$id.'/contexto/editar#preparation-questions'];
}
return ['private_investment' => $preparation, 'case' => ['id' => $id, 'title' => $case->title, 'version' => $case->version, 'closed' => $case->closed_at !== null],
$preparationTaskKeys = $opportunity !== null && ($owner || $manager) && app(PrivateInvestmentPreparation::class)->available() ? app(InvestmentPreparationWorkspace::class)->taskKeys($actor, $id, $opportunity) : null;
return ['funding_selections' => ($owner || $manager) ? array_values(array_filter($path['funding_selections'] ?? [], fn ($row) => ($row['opportunity'] ?? null) === $opportunity)) : [], 'preparation_action' => ($owner || $manager) && app(PrivateInvestmentPreparation::class)->available() ? app(InvestmentPreparationWorkspace::class)->continuation($actor, $id, $opportunity, request()->query('swot_action')) : null, 'private_investment' => $preparation, 'case' => ['id' => $id, 'title' => $case->title, 'version' => $case->version, 'closed' => $case->closed_at !== null],
'journey_id' => $journey->id, 'definition_id' => $version->id, 'definition_version' => $version->version_no, 'source_sha256' => $definition['source_sha256'],
'phases' => $definition['phases'], 'steps' => $steps, 'facts' => $facts, 'fact_reasons' => $factReasons,
'context_opportunity' => $opportunity, 'opportunity_options' => $allChoices,
@@ -174,10 +176,12 @@ final class InvestorPath
'content_sources' => ($owner || $manager) ? ($path['content_sources'] ?? []) : [],
'tasks' => DB::table('tasks')->where('desk_case_id', $id)
->whereNull('deleted_at')
->when($preparationTaskKeys !== null, fn ($q) => $q->where(fn ($p) => $p->whereNull('guidance_key')->orWhere('guidance_key', 'not like', 'swot:%')->orWhereIn('guidance_key', $preparationTaskKeys ?? [])))
->when(! $owner && ! $manager, fn ($q) => $q->where('assignee_user_id', $actor->id))
->get(['id', 'title', 'status', 'version_no', 'due_at', 'assignee_user_id']),
'services' => app(DeskServiceAvailability::class)->current()->get()->map(fn ($s) => ['id' => $s->id, 'title' => json_decode($s->definition, true)['title'], 'effective_from' => $s->effective_from ?? null]),
'tools' => ['simulator' => (int) $case->user_id === $actor->id ? '/simulador-fiscal?context='.$id.($case->simulation_id ? '&draft='.$case->simulation_id : '').($opportunity ? '&opportunity='.urlencode($opportunity) : '') : null,
'tools' => ['funding' => ($owner || $manager) && app(FundingProgrammes::class)->installed() ? '/fundos?kind=institution&location_mode=physical&context='.$id.($opportunity ? '&opportunity='.urlencode($opportunity) : '') : null, 'simulator' => (int) $case->user_id === $actor->id ? '/simulador-fiscal?context='.$id.($case->simulation_id ? '&draft='.$case->simulation_id : '').($opportunity ? '&opportunity='.urlencode($opportunity) : '') : null,
'preparation' => ($owner || $manager) && app(PrivateInvestmentPreparation::class)->available() ? '/investimentos/'.$id.'/preparacao'.($opportunity ? '?opportunity='.urlencode($opportunity) : '') : null,
'company' => '/investimentos/'.$id.'/forma-juridica',
'desk' => '/balcao-virtual/dossiers/'.$id.($opportunity ? '?opportunity='.urlencode($opportunity) : ''), 'guidance' => '/balcao-virtual/dossiers/'.$id.'/orientacao',
'library' => '/documentos?context='.$id, 'opportunities' => '/oportunidades?context='.$id, 'territory' => '/zonas?context='.$id, 'partners' => '/parcerias/painel?context='.$id,
@@ -337,12 +341,16 @@ final class InvestorPath
public function task(User $actor, string $id, array $input): string
{
$data = Validator::make($input, ['version' => 'required|integer|min:1', 'code' => ['required', Rule::in(array_map(fn ($n) => sprintf('S%02d', $n), range(1, 19)))],
'preparation_action' => 'nullable|uuid', 'opportunity' => 'nullable|string|max:150',
'title' => 'required|string|min:3|max:200', 'due_at' => 'required|date|after:now', 'consent' => 'accepted'])->validate();
return DB::transaction(function () use ($actor, $id, $data): string {
$case = $this->desk->case($actor, $id, true, true);
abort_if($case->closed_at, 409);
$key = 'path:'.$data['code'];
if (!empty($data['preparation_action'])) { app(InvestmentConsumerScope::class)->key(app(InvestorContext::class)->state($case), $data['opportunity'] ?? null, true); }
$origin = !empty($data['preparation_action']) ? app(InvestmentPreparationWorkspace::class)->continuation($actor, $id, $data['opportunity'] ?? null, $data['preparation_action']) : null;
abort_if(!empty($data['preparation_action']) && (!$origin || $origin['kind'] !== 'risk'), 422, 'A acção SWOT não pertence a este dossier/oportunidade ou não é uma acção de risco.');
$key = $origin ? 'swot:'.$origin['id'] : 'path:'.$data['code'];
$existing = DB::table('tasks')->where('desk_case_id', $id)->where('guidance_key', $key)->first();
if ($existing) {
abort_unless($existing->title === $data['title'] && (int) $existing->accepted_by === $actor->id
@@ -351,12 +359,12 @@ final class InvestorPath
return $existing->id;
}
$this->writable($actor, $id, $data['version']);
$step = $this->findStep($this->view($actor, $id), $data['code']);
$step = $this->findStep($this->view($actor, $id, $origin['opportunity'] ?? null), $data['code']);
abort_unless($step !== null && $step['preparation_allowed'], 422);
$task = CrmTask::create(['desk_case_id' => $id, 'guidance_key' => $key, 'title' => $data['title'], 'due_at' => $data['due_at'],
'status' => 'open', 'assignee_user_id' => $actor->id, 'accepted_by' => $actor->id, 'accepted_at' => now(),
'external_sync_status' => 'pending_authorization', 'data_classification' => 'confidential', 'source_system' => 'investor-path']);
$this->desk->event($case, $actor, 'path_task_accepted', ['task_id' => $task->id, 'code' => $data['code'], 'scope' => 'internal_crm']);
$this->desk->event($case, $actor, 'path_task_accepted', ['task_id' => $task->id, 'code' => $data['code'], 'scope' => 'internal_crm', 'preparation_action' => $origin['id'] ?? null, 'opportunity' => $origin['opportunity'] ?? null, 'swot_review_version' => $origin['review_version'] ?? null]);
return $task->id;
});
+132
View File
@@ -0,0 +1,132 @@
<?php
namespace App\Services;
use App\Models\Opportunity;
use App\Models\User;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
class OpportunityCuration
{
public function __construct(private PortalProfileAccess $access, private OpportunityManagement $management) {}
public function allowed(User $actor, Opportunity $record): bool
{
if (! $this->management->installed() || ! $this->access->canView($actor, $record)) {
return false;
}
if ($record->owner_user_id === null) {
return $this->access->legacyWriteAllowed($actor);
}
return $this->access->scoped($actor, 'publisher', $record->organization_id, $record->opportunity_category)
&& $this->access->operational($actor, 'publish', $record->id) && ! $this->access->conflict($actor, $record)
&& (int) data_get($record->workflow_context, 'approval.actor_id') !== $actor->id
&& (! data_get($record->workflow_context, 'publication_assignment.assignee_user_id')
|| (int) data_get($record->workflow_context, 'publication_assignment.assignee_user_id') === $actor->id);
}
private function authorize(User $actor, Opportunity $record): void
{
abort_unless($this->allowed($actor, $record), 403);
if ($record->owner_user_id !== null) {
app(OpportunityProfileWorkflow::class)->authorizeEditorialCuration($actor, $record);
}
}
/** @return array<string,mixed> */
public function state(User $actor, int $id, int $page = 1): array
{
$record = Opportunity::findOrFail($id);
$this->authorize($actor, $record);
$history = DB::table('audit_events')->where('source_system', 'opportunity-curation')->where('after_data->record_id', $id)
->orderByDesc('id')->paginate(25, ['id', 'actor_user_id', 'occurred_at', 'after_data'], 'page', $page)->through(function ($row) {
$data = json_decode($row->after_data, true, 512, JSON_THROW_ON_ERROR);
return ['id' => $row->id, 'actor' => $row->actor_user_id, 'at' => $row->occurred_at,
'featured' => $data['featured'], 'reason' => Crypt::decryptString($data['reason'])];
});
return ['record' => ['id' => $record->id, 'title' => $record->title, 'featured' => $record->is_featured],
'etag' => $this->management->etag($record), 'history' => $history];
}
/** @param array<string,mixed> $input
* @return array<string,mixed>
*/
public function preview(User $actor, int $id, array $input): array
{
$data = $this->validate($input);
$record = Opportunity::findOrFail($id);
$this->authorize($actor, $record);
$this->check($record, $data);
return ['hash' => $this->hash($actor, $id, $data), 'title' => $record->title,
'before' => $record->is_featured, 'featured' => $data['featured'], 'reason' => $data['reason']];
}
/** @param array<string,mixed> $input
* @return array<string,mixed>
*/
public function confirm(User $actor, int $id, array $input): array
{
$data = $this->validate($input);
Validator::make($input, ['hash' => 'required|string|size:64', 'confirm' => 'required|accepted'])->validate();
abort_unless(hash_equals($this->hash($actor, $id, $data), $input['hash']), 409, 'A conferência mudou; prepare nova prévia.');
return DB::transaction(function () use ($actor, $id, $data) {
$record = Opportunity::whereKey($id)->lockForUpdate()->firstOrFail();
$this->authorize($actor, $record);
$receipt = DB::table('audit_events')->where('source_system', 'opportunity-curation')->where('after_data->record_id', $id)
->where('after_data->client_key', $data['client_key'])->first(['actor_user_id', 'after_data']);
$intent = $this->hash($actor, $id, $data);
if ($receipt) {
$prior = json_decode($receipt->after_data, true, 512, JSON_THROW_ON_ERROR);
abort_unless((int) $receipt->actor_user_id === $actor->id && hash_equals($prior['intent'], $intent), 409, 'Chave já utilizada com outro conteúdo.');
return ['replayed' => true, 'featured' => $record->is_featured, 'etag' => $this->management->etag($record)];
}
$this->check($record, $data);
$before = $record->is_featured;
$record->management_version++;
PortalAudit::save($record, ['is_featured' => $data['featured']], $actor->id);
DB::table('audit_events')->insert(['source_system' => 'opportunity-curation', 'entity_type' => 'opportunities',
'actor_user_id' => $actor->id, 'action' => 'featured_changed', 'data_classification' => 'restricted',
'before_data' => json_encode(['record_id' => $id, 'featured' => $before], JSON_THROW_ON_ERROR),
'after_data' => json_encode(['record_id' => $id, 'featured' => $record->is_featured, 'version' => $record->management_version,
'reason' => Crypt::encryptString($data['reason']), 'client_key' => $data['client_key'], 'intent' => $intent], JSON_THROW_ON_ERROR)]);
return ['replayed' => false, 'featured' => $record->is_featured, 'etag' => $this->management->etag($record)];
});
}
/** @param array<string,mixed> $input
* @return array{etag:string,client_key:string,featured:bool,reason:string}
*/
private function validate(array $input): array
{
$data = Validator::make($input, ['etag' => 'required|string|size:64', 'client_key' => 'required|uuid',
'featured' => 'required|boolean', 'reason' => 'required|string|min:20|max:1000'])->validate();
return ['etag' => $data['etag'], 'client_key' => $data['client_key'],
'featured' => (bool) $data['featured'], 'reason' => $data['reason']];
}
/** @param array{etag:string,client_key:string,featured:bool,reason:string} $data */
private function check(Opportunity $record, array $data): void
{
abort_unless(hash_equals($this->management->etag($record), $data['etag']), 409, 'A oportunidade mudou; recupere a versão actual antes de confirmar.');
abort_if($record->is_featured === $data['featured'], 422, 'O estado de destaque já corresponde à escolha.');
if ($data['featured']) {
abort_unless(Opportunity::published()->whereKey($record->id)->exists(), 409, 'Só uma oportunidade actualmente pública pode entrar em destaque.');
}
}
/** @param array{etag:string,client_key:string,featured:bool,reason:string} $data */
private function hash(User $actor, int $id, array $data): string
{
return hash_hmac('sha256', json_encode([$actor->id, $id, $data], JSON_THROW_ON_ERROR), (string) config('app.key'));
}
}
@@ -16,6 +16,11 @@ class OpportunityProfileWorkflow
public function __construct(private PortalProfileAccess $access, private OpportunityManagement $management,
private OpportunityCanonicalContract $contract, private OpportunityWorkflowTasks $tasks) {}
public function authorizeEditorialCuration(User $actor, Opportunity $record): void
{
$this->authorizeAction($actor, $record, 'publish');
}
/** @return list<string> */
public function actions(User $actor, Opportunity $record): array
{
+2
View File
@@ -22,6 +22,8 @@ class PortalProfileAccess
app_path('Services/PortalProfileAdministration.php'), app_path('Services/PortalSuperusers.php'),
app_path('Services/OpportunityProfileWorkflow.php'), app_path('Http/Controllers/PortalProfileController.php'), app_path('Http/Controllers/DashboardController.php'),
app_path('Http/Controllers/OpportunityManagementController.php'),
app_path('Services/OpportunityCuration.php'), app_path('Http/Controllers/OpportunityCurationController.php'),
resource_path('js/pages/management/opportunity-curation.tsx'),
app_path('Services/OpportunityCanonicalContract.php'), app_path('Services/OpportunityCanonicalResponse.php'), app_path('Services/OpportunityCategoryFields.php'), app_path('Services/OpportunityModularProfiles.php'), app_path('Services/OpportunityWorkflowTasks.php'), base_path('composer.json'), base_path('composer.lock'),
app_path('Services/OpportunityReviewMateriality.php'), app_path('Services/OpportunityReviewConditions.php'), app_path('Services/OpportunityCanonicalMetadata.php'), app_path('Services/OpportunityCompleteness.php'), app_path('Services/OpportunityCanonicalValue.php'),
resource_path('data/investmoz-opportunity-modular.schema.json'), resource_path('js/components/opportunity-canonical-fields.tsx'), resource_path('js/components/opportunity-parcel-geometry.tsx'),
+23
View File
@@ -0,0 +1,23 @@
<?php
namespace App\Support;
use Generator;
final class PublicSitemapXml
{
/** @param iterable<string> $locations
* @return Generator<int, string>
*/
public function chunks(iterable $locations, bool $index = false): Generator
{
$root = $index ? 'sitemapindex' : 'urlset';
$item = $index ? 'sitemap' : 'url';
yield '<?xml version="1.0" encoding="UTF-8"?>'."\n";
yield '<'.$root.' xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">'."\n";
foreach ($locations as $location) {
yield '<'.$item.'><loc>'.htmlspecialchars($location, ENT_XML1 | ENT_QUOTES, 'UTF-8').'</loc></'.$item.'>'."\n";
}
yield '</'.$root.'>';
}
}
+1 -1
View File
@@ -21,7 +21,7 @@ return [
// Experimental proposal only; functional approval and operational SLA remain pending.
'opportunity_modular_profiles_qa_enabled' => env('PORTAL_OPPORTUNITY_MODULAR_PROFILES_QA_ENABLED', false),
'opportunity_other_qa_subtypes' => [],
'operator_name' => env('PORTAL_OPERATOR_NAME'),
'operator_name' => env('PORTAL_OPERATOR_NAME') ?: 'Tempo, Lda.',
// Tempo, 19/10/2026: institutional contact; commercial department remains separate.
'institutional_contact_email' => env('PORTAL_INSTITUTIONAL_CONTACT_EMAIL', 'comercial@tempo.co.mz'),
'support' => [
File diff suppressed because it is too large Load Diff
+806 -9
View File
@@ -4,8 +4,18 @@
"fdel": {
"edition": "fdel-br43-20250305-tete20251020-v1",
"provenance": [
{ "url": "https://mpd.gov.mz/wp-content/uploads/2025/10/BR_43_I_SERIE_2025.pdf", "sha256": "0ea99c5f894760e3b715e377e87e9715fad9423e322e5753de1b9bb345c64753", "checked_on": "2026-11-02", "reference": "BR I Série n.º 43, 05/03/2025, pp. 157–160; Decreto n.º 4/2025 e Regulamento anexo" },
{ "url": "https://cmct.gov.mz/fundo-de-desenvolvimento-economico-local-fdel/", "sha256": "c69c2c3883766aa6a238ae44d8ee3fc99570adca2f3e647e50df59fde2786403", "checked_on": "2026-11-02", "reference": "Aviso municipal FDEL, prazo 20/10/2025" }
{
"url": "https://mpd.gov.mz/wp-content/uploads/2025/10/BR_43_I_SERIE_2025.pdf",
"sha256": "0ea99c5f894760e3b715e377e87e9715fad9423e322e5753de1b9bb345c64753",
"checked_on": "2026-11-02",
"reference": "BR I Série n.º 43, 05/03/2025, pp. 157–160; Decreto n.º 4/2025 e Regulamento anexo"
},
{
"url": "https://cmct.gov.mz/fundo-de-desenvolvimento-economico-local-fdel/",
"sha256": "c69c2c3883766aa6a238ae44d8ee3fc99570adca2f3e647e50df59fde2786403",
"checked_on": "2026-11-02",
"reference": "Aviso municipal FDEL, prazo 20/10/2025"
}
],
"draft": {
"title": "Fundo de Desenvolvimento Económico Local — FDEL",
@@ -25,8 +35,22 @@
"sector_ids": [],
"resource_ids": [],
"criteria": [
{ "code": "nationality-individual", "question": "Tem nacionalidade moçambicana?", "applies_to": "individual", "expected": "yes", "source_reference": "Decreto n.º 4/2025, art. 3", "explanation": "Condição declarada; não substitui a apreciação competente." },
{ "code": "economic-viability", "question": "O projecto apresenta elementos de viabilidade económica?", "applies_to": "all", "expected": "yes", "source_reference": "Decreto n.º 4/2025, art. 3; Regulamento, art. 13", "explanation": "A avaliação do projecto cabe às entidades competentes." }
{
"code": "nationality-individual",
"question": "Tem nacionalidade moçambicana?",
"applies_to": "individual",
"expected": "yes",
"source_reference": "Decreto n.º 4/2025, art. 3",
"explanation": "Condição declarada; não substitui a apreciação competente."
},
{
"code": "economic-viability",
"question": "O projecto apresenta elementos de viabilidade económica?",
"applies_to": "all",
"expected": "yes",
"source_reference": "Decreto n.º 4/2025, art. 3; Regulamento, art. 13",
"explanation": "A avaliação do projecto cabe às entidades competentes."
}
],
"windows": [
{
@@ -42,15 +66,788 @@
"checked_on": "2026-11-02",
"application_url": "https://cmct.gov.mz/anexo-5-plano-de-negocio/",
"criteria": [
{ "code": "tete-nuit", "question": "Dispõe de NUIT?", "applies_to": "all", "expected": "yes", "source_reference": "Aviso municipal, Requisitos", "explanation": "Documentação indicada para esta chamada histórica." },
{ "code": "tete-identity", "question": "Dispõe de cópia do BI?", "applies_to": "individual", "expected": "yes", "source_reference": "Aviso municipal, Candidatos individuais", "explanation": "Aplica-se a pessoas singulares nesta chamada." },
{ "code": "tete-registration", "question": "A entidade dispõe de comprovativo de registo legal?", "applies_to": "entity", "expected": "yes", "source_reference": "Aviso municipal, Entidades", "explanation": "Aplica-se às entidades indicadas no aviso." },
{ "code": "tete-idoneity", "question": "Dispõe de atestado de idoneidade emitido pela sede do bairro?", "applies_to": "individual", "expected": "yes", "source_reference": "Aviso municipal, Candidatos individuais", "explanation": "Documento indicado no aviso." },
{ "code": "tete-national-entity", "question": "A entidade é constituída por cidadãos nacionais?", "applies_to": "entity", "expected": "yes", "source_reference": "Aviso municipal, Entidades", "explanation": "Condição específica desta chamada." }
{
"code": "tete-nuit",
"question": "Dispõe de NUIT?",
"applies_to": "all",
"expected": "yes",
"source_reference": "Aviso municipal, Requisitos",
"explanation": "Documentação indicada para esta chamada histórica."
},
{
"code": "tete-identity",
"question": "Dispõe de cópia do BI?",
"applies_to": "individual",
"expected": "yes",
"source_reference": "Aviso municipal, Candidatos individuais",
"explanation": "Aplica-se a pessoas singulares nesta chamada."
},
{
"code": "tete-registration",
"question": "A entidade dispõe de comprovativo de registo legal?",
"applies_to": "entity",
"expected": "yes",
"source_reference": "Aviso municipal, Entidades",
"explanation": "Aplica-se às entidades indicadas no aviso."
},
{
"code": "tete-idoneity",
"question": "Dispõe de atestado de idoneidade emitido pela sede do bairro?",
"applies_to": "individual",
"expected": "yes",
"source_reference": "Aviso municipal, Candidatos individuais",
"explanation": "Documento indicado no aviso."
},
{
"code": "tete-national-entity",
"question": "A entidade é constituída por cidadãos nacionais?",
"applies_to": "entity",
"expected": "yes",
"source_reference": "Aviso municipal, Entidades",
"explanation": "Condição específica desta chamada."
}
]
}
]
}
},
"bci": {
"edition": "tempo-benchmark-20261004-directory-20261110-v1",
"provenance": [
{
"reference": "benchmark de melhorias investeMoz.docx; quadro comparativo 04/10/2026",
"sha256": "065203aacfae766c62cf9c2c272ebacc61ff23bd4ff9e91535939fede87f7617",
"checked_on": "2026-11-10"
},
{
"reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; BCI Exclusivo PME",
"url": "https://www.bci.co.mz/bci-exclusivo-pme/",
"checked_on": "2026-11-10"
}
],
"draft": {
"title": "BCI",
"kind": "institution",
"summary": "Conta corrente caucionada, empréstimo de médio/longo prazo, garantias e comércio externo. Público/finalidade: ENI, micro, pequenas e médias empresas.. Referência informativa, não concessão de financiamento.",
"manager_name": "BCI",
"source_url": "https://www.bci.co.mz/bci-exclusivo-pme/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; BCI Exclusivo PME",
"source_checked_on": "2026-11-10",
"source_status": "identified",
"coverage": "national",
"coverage_text": "Nacional; atendimento conforme rede e canais do banco.",
"territory_ids": [],
"sectors_scope": "unknown",
"sectors_text": "Critérios sectoriais por conferir no produto/programa; nenhuma elegibilidade automática.",
"sector_ids": [],
"resource_ids": [],
"criteria": [],
"windows": [],
"funding_needs": [
"working_capital",
"investment",
"guarantee"
],
"institution_key": "bci",
"institution_nature": "bank",
"contacts": [
{
"source_url": "https://www.bci.co.mz/bci-exclusivo-pme/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; BCI Exclusivo PME",
"checked_on": "2026-11-10",
"kind": "website",
"value": "https://www.bci.co.mz/bci-exclusivo-pme/"
}
],
"locations": [],
"products": [
{
"source_url": "https://www.bci.co.mz/bci-exclusivo-pme/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; BCI Exclusivo PME",
"checked_on": "2026-11-10",
"code": "bci-oferta",
"title": "BCI — Soluções PME",
"instrument": "Conta corrente caucionada, empréstimo de médio/longo prazo, garantias e comércio externo.",
"needs": [
"working_capital",
"investment",
"guarantee"
],
"coverage": "national",
"coverage_text": "Nacional; atendimento conforme rede e canais do banco.",
"territory_ids": [],
"conditions": "ENI, micro, pequenas e médias empresas. Oferta permanente publicada. A antiga “Linha BCI Negócios PME” terminou em 31 de Dezembro de 2018 e fica excluída das oportunidades activas.",
"amounts": "Variável segundo necessidade, produto e análise.",
"availability": "documented",
"contact_url": "https://www.bci.co.mz/bci-exclusivo-pme/"
}
]
}
},
"millennium-bim": {
"edition": "tempo-benchmark-20261004-directory-20261110-v1",
"provenance": [
{
"reference": "benchmark de melhorias investeMoz.docx; quadro comparativo 04/10/2026",
"sha256": "065203aacfae766c62cf9c2c272ebacc61ff23bd4ff9e91535939fede87f7617",
"checked_on": "2026-11-10"
},
{
"reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Financiamento para empresas",
"url": "https://www.millenniumbim.co.mz/pt/empresas/produtos/financiamento",
"checked_on": "2026-11-10"
}
],
"draft": {
"title": "Millennium bim",
"kind": "institution",
"summary": "Conta empréstimo, conta corrente caucionada, factoring, confirming , leasing e garantias. Público/finalidade: Empresas e ENI elegíveis.. Referência informativa, não concessão de financiamento.",
"manager_name": "Millennium bim",
"source_url": "https://www.millenniumbim.co.mz/pt/empresas/produtos/financiamento",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Financiamento para empresas",
"source_checked_on": "2026-11-10",
"source_status": "identified",
"coverage": "national",
"coverage_text": "Nacional; balcões e canais do banco.",
"territory_ids": [],
"sectors_scope": "unknown",
"sectors_text": "Critérios sectoriais por conferir no produto/programa; nenhuma elegibilidade automática.",
"sector_ids": [],
"resource_ids": [],
"criteria": [],
"windows": [],
"funding_needs": [
"working_capital",
"investment",
"guarantee"
],
"institution_key": "millennium-bim",
"institution_nature": "bank",
"contacts": [
{
"source_url": "https://www.millenniumbim.co.mz/pt/empresas/produtos/financiamento",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Financiamento para empresas",
"checked_on": "2026-11-10",
"kind": "website",
"value": "https://www.millenniumbim.co.mz/pt/empresas/produtos/financiamento"
}
],
"locations": [],
"products": [
{
"source_url": "https://www.millenniumbim.co.mz/pt/empresas/produtos/financiamento",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Financiamento para empresas",
"checked_on": "2026-11-10",
"code": "millennium-bim-oferta",
"title": "Millennium bim — Financiamento empresarial",
"instrument": "Conta empréstimo, conta corrente caucionada, factoring, confirming , leasing e garantias.",
"needs": [
"working_capital",
"investment",
"guarantee"
],
"coverage": "national",
"coverage_text": "Nacional; balcões e canais do banco.",
"territory_ids": [],
"conditions": "Empresas e ENI elegíveis. Oferta permanente publicada. Condições finais dependem da análise de risco e contrato.",
"amounts": "Conta empréstimo: investimento entre 3 e 180 meses na ficha publicada; restantes termos após análise.",
"availability": "documented",
"contact_url": "https://www.millenniumbim.co.mz/pt/empresas/produtos/financiamento"
}
]
}
},
"absa": {
"edition": "tempo-benchmark-20261004-directory-20261110-v1",
"provenance": [
{
"reference": "benchmark de melhorias investeMoz.docx; quadro comparativo 04/10/2026",
"sha256": "065203aacfae766c62cf9c2c272ebacc61ff23bd4ff9e91535939fede87f7617",
"checked_on": "2026-11-10"
},
{
"reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Empréstimos comerciais",
"url": "https://www.absa.co.mz/pt/banca-de-negocios/borrow/",
"checked_on": "2026-11-10"
}
],
"draft": {
"title": "Absa",
"kind": "institution",
"summary": "CAPEX , activos, importação, descoberto e leasing. Público/finalidade: PME e empresas com capacidade de endividamento.. Referência informativa, não concessão de financiamento.",
"manager_name": "Absa",
"source_url": "https://www.absa.co.mz/pt/banca-de-negocios/borrow/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Empréstimos comerciais",
"source_checked_on": "2026-11-10",
"source_status": "identified",
"coverage": "national",
"coverage_text": "Nacional; localização da empresa é recolhida no pedido.",
"territory_ids": [],
"sectors_scope": "unknown",
"sectors_text": "Critérios sectoriais por conferir no produto/programa; nenhuma elegibilidade automática.",
"sector_ids": [],
"resource_ids": [],
"criteria": [],
"windows": [],
"funding_needs": [
"working_capital",
"investment"
],
"institution_key": "absa",
"institution_nature": "bank",
"contacts": [
{
"source_url": "https://www.absa.co.mz/pt/banca-de-negocios/borrow/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Empréstimos comerciais",
"checked_on": "2026-11-10",
"kind": "website",
"value": "https://www.absa.co.mz/pt/banca-de-negocios/borrow/"
},
{
"source_url": "https://www.absa.co.mz/pt/banca-de-negocios/borrow/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Empréstimos comerciais",
"checked_on": "2026-11-10",
"kind": "phone",
"value": "+258 21 344 400"
},
{
"source_url": "https://www.absa.co.mz/pt/banca-de-negocios/borrow/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Empréstimos comerciais",
"checked_on": "2026-11-10",
"kind": "email",
"value": "linhacliente@absa.africa"
}
],
"locations": [],
"products": [
{
"source_url": "https://www.absa.co.mz/pt/banca-de-negocios/borrow/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Empréstimos comerciais",
"checked_on": "2026-11-10",
"code": "absa-oferta",
"title": "Absa — Empréstimos PME e activos",
"instrument": "CAPEX , activos, importação, descoberto e leasing.",
"needs": [
"working_capital",
"investment"
],
"coverage": "national",
"coverage_text": "Nacional; localização da empresa é recolhida no pedido.",
"territory_ids": [],
"conditions": "PME e empresas com capacidade de endividamento. Oferta permanente publicada. Critérios, documentos, garantias e ausência de registo desabonatório aplicam-se conforme produto.",
"amounts": "Leasing com prazo publicado até 5 anos; demais condições conforme análise.",
"availability": "documented",
"contact_url": "https://www.absa.co.mz/pt/banca-de-negocios/borrow/"
}
]
}
},
"standard-bank": {
"edition": "tempo-benchmark-20261004-directory-20261110-v1",
"provenance": [
{
"reference": "benchmark de melhorias investeMoz.docx; quadro comparativo 04/10/2026",
"sha256": "065203aacfae766c62cf9c2c272ebacc61ff23bd4ff9e91535939fede87f7617",
"checked_on": "2026-11-10"
},
{
"reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Conta Empréstimo",
"url": "https://www.standardbank.co.mz/Produtos/Empresas/Conta-Emprestimo",
"checked_on": "2026-11-10"
}
],
"draft": {
"title": "Standard Bank",
"kind": "institution",
"summary": "Crédito de médio/longo prazo para bens de capital e CAPEX . Público/finalidade: Empresas com capacidade de endividamento.. Referência informativa, não concessão de financiamento.",
"manager_name": "Standard Bank",
"source_url": "https://www.standardbank.co.mz/Produtos/Empresas/Conta-Emprestimo",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Conta Empréstimo",
"source_checked_on": "2026-11-10",
"source_status": "identified",
"coverage": "national",
"coverage_text": "Nacional; balcões e canais empresariais.",
"territory_ids": [],
"sectors_scope": "unknown",
"sectors_text": "Critérios sectoriais por conferir no produto/programa; nenhuma elegibilidade automática.",
"sector_ids": [],
"resource_ids": [],
"criteria": [],
"windows": [],
"funding_needs": [
"investment"
],
"institution_key": "standard-bank",
"institution_nature": "bank",
"contacts": [
{
"source_url": "https://www.standardbank.co.mz/Produtos/Empresas/Conta-Emprestimo",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Conta Empréstimo",
"checked_on": "2026-11-10",
"kind": "website",
"value": "https://www.standardbank.co.mz/Produtos/Empresas/Conta-Emprestimo"
},
{
"source_url": "https://www.standardbank.co.mz/Produtos/Empresas/Conta-Emprestimo",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Conta Empréstimo",
"checked_on": "2026-11-10",
"kind": "phone",
"value": "+258 21 355700"
}
],
"locations": [],
"products": [
{
"source_url": "https://www.standardbank.co.mz/Produtos/Empresas/Conta-Emprestimo",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Conta Empréstimo",
"checked_on": "2026-11-10",
"code": "standard-bank-oferta",
"title": "Standard Bank — Conta Empréstimo Empresas",
"instrument": "Crédito de médio/longo prazo para bens de capital e CAPEX .",
"needs": [
"investment"
],
"coverage": "national",
"coverage_text": "Nacional; balcões e canais empresariais.",
"territory_ids": [],
"conditions": "Empresas com capacidade de endividamento. Produto activo e publicado. Sujeito a análise e garantias.",
"amounts": "Até 7 anos; limite definido segundo capacidade e investimento; garantias reais.",
"availability": "documented",
"contact_url": "https://www.standardbank.co.mz/Produtos/Empresas/Conta-Emprestimo"
}
]
}
},
"moza": {
"edition": "tempo-benchmark-20261004-directory-20261110-v1",
"provenance": [
{
"reference": "benchmark de melhorias investeMoz.docx; quadro comparativo 04/10/2026",
"sha256": "065203aacfae766c62cf9c2c272ebacc61ff23bd4ff9e91535939fede87f7617",
"checked_on": "2026-11-10"
},
{
"reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Financiamento empresarial",
"url": "https://www.mozabanco.co.mz/pt/empresas/investimento/financiamento/",
"checked_on": "2026-11-10"
}
],
"draft": {
"title": "Moza Banco",
"kind": "institution",
"summary": "Tesouraria, investimento, construção e equipamento. Público/finalidade: Pequenas e médias empresas.. Referência informativa, não concessão de financiamento.",
"manager_name": "Moza Banco",
"source_url": "https://www.mozabanco.co.mz/pt/empresas/investimento/financiamento/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Financiamento empresarial",
"source_checked_on": "2026-11-10",
"source_status": "identified",
"coverage": "national",
"coverage_text": "Nacional; sem restrição territorial indicada no produto geral.",
"territory_ids": [],
"sectors_scope": "unknown",
"sectors_text": "Critérios sectoriais por conferir no produto/programa; nenhuma elegibilidade automática.",
"sector_ids": [],
"resource_ids": [],
"criteria": [],
"windows": [],
"funding_needs": [
"working_capital",
"investment"
],
"institution_key": "moza",
"institution_nature": "bank",
"contacts": [
{
"source_url": "https://www.mozabanco.co.mz/pt/empresas/investimento/financiamento/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Financiamento empresarial",
"checked_on": "2026-11-10",
"kind": "website",
"value": "https://www.mozabanco.co.mz/pt/empresas/investimento/financiamento/"
}
],
"locations": [],
"products": [
{
"source_url": "https://www.mozabanco.co.mz/pt/empresas/investimento/financiamento/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Financiamento empresarial",
"checked_on": "2026-11-10",
"code": "moza-oferta",
"title": "Moza Banco — Crédito PME",
"instrument": "Tesouraria, investimento, construção e equipamento.",
"needs": [
"working_capital",
"investment"
],
"coverage": "national",
"coverage_text": "Nacional; sem restrição territorial indicada no produto geral.",
"territory_ids": [],
"conditions": "Pequenas e médias empresas. Produto apresentado no portal. A ficha contém condições que devem ser reconfirmadas antes da publicação, incluindo montante, comissão e garantias.",
"amounts": "Ficha publicada: 500.000–30.000.000 MT; 6–60 meses; Prime + spread.",
"availability": "unknown",
"contact_url": "https://www.mozabanco.co.mz/pt/empresas/investimento/financiamento/"
}
]
}
},
"bni": {
"edition": "tempo-benchmark-20261004-directory-20261110-v1",
"provenance": [
{
"reference": "benchmark de melhorias investeMoz.docx; quadro comparativo 04/10/2026",
"sha256": "065203aacfae766c62cf9c2c272ebacc61ff23bd4ff9e91535939fede87f7617",
"checked_on": "2026-11-10"
},
{
"reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Banca de Desenvolvimento",
"url": "https://www.bni.co.mz/banca-de-desenvolvimento/",
"checked_on": "2026-11-10"
},
{
"reference": "BNI Capital: veículo do BNI, registado como produto sem duplicar a instituição",
"url": "https://www.bni.co.mz/banca-de-investimento/capital-de-risco/",
"checked_on": "2026-11-10"
}
],
"draft": {
"title": "BNI",
"kind": "institution",
"summary": "Crédito para sectores produtivos, infra-estruturas e projectos com impacto. Público/finalidade: Empresas e projectos de desenvolvimento.. Referência informativa, não concessão de financiamento.",
"manager_name": "BNI",
"source_url": "https://www.bni.co.mz/banca-de-desenvolvimento/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Banca de Desenvolvimento",
"source_checked_on": "2026-11-10",
"source_status": "identified",
"coverage": "national",
"coverage_text": "Nacional ou conforme fundo/linha específica.",
"territory_ids": [],
"sectors_scope": "unknown",
"sectors_text": "Critérios sectoriais por conferir no produto/programa; nenhuma elegibilidade automática.",
"sector_ids": [],
"resource_ids": [],
"criteria": [],
"windows": [],
"funding_needs": [
"investment"
],
"institution_key": "bni",
"institution_nature": "bank",
"contacts": [
{
"source_url": "https://www.bni.co.mz/banca-de-desenvolvimento/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Banca de Desenvolvimento",
"checked_on": "2026-11-10",
"kind": "website",
"value": "https://www.bni.co.mz/banca-de-desenvolvimento/"
},
{
"source_url": "https://www.bni.co.mz/contactos/",
"source_reference": "BNI — Contactos, endereço e telefone",
"checked_on": "2026-11-10",
"kind": "phone",
"value": "+258 21 498 581"
}
],
"locations": [
{
"source_url": "https://www.bni.co.mz/contactos/",
"source_reference": "BNI — Contactos, endereço e telefone",
"checked_on": "2026-11-10",
"role": "contact_point",
"label": "BNI — endereço institucional de Maputo",
"address": "Av. Julius Nyerere, 3504, Bloco A2, 4668 Maputo, Moçambique",
"territory_id": null
}
],
"products": [
{
"source_url": "https://www.bni.co.mz/banca-de-desenvolvimento/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Banca de Desenvolvimento",
"checked_on": "2026-11-10",
"code": "bni-oferta",
"title": "BNI — Crédito de desenvolvimento",
"instrument": "Crédito para sectores produtivos, infra-estruturas e projectos com impacto.",
"needs": [
"investment"
],
"coverage": "national",
"coverage_text": "Nacional ou conforme fundo/linha específica.",
"territory_ids": [],
"conditions": "Empresas e projectos de desenvolvimento. Serviço institucional publicado. Cada linha deve ter o seu próprio estado e regulamento.",
"amounts": "Definido por operação; o simulador oficial é apenas indicativo.",
"availability": "documented",
"contact_url": "https://www.bni.co.mz/banca-de-desenvolvimento/"
},
{
"source_url": "https://www.bni.co.mz/banca-de-investimento/capital-de-risco/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Capital de Risco",
"checked_on": "2026-11-10",
"code": "bni-capital-oferta",
"title": "BNI Capital",
"instrument": "Participação temporária e geralmente minoritária no capital; investimento em fundos.",
"needs": [
"equity"
],
"coverage": "national",
"coverage_text": "Moçambique, conforme tese e avaliação de investimento.",
"territory_ids": [],
"conditions": "Empresas não cotadas com crescimento, impacto, gestão profissional e insuficiência de capital. Serviço institucional publicado. Manifestação e processo dependem de avaliação do BNI .",
"amounts": "Negociado por operação; não é crédito.",
"availability": "documented",
"contact_url": "https://www.bni.co.mz/banca-de-investimento/capital-de-risco/"
}
]
},
"location_territory_names": {
"0": "Maputo Cidade"
}
},
"gapi": {
"edition": "tempo-benchmark-20261004-directory-20261110-v1",
"provenance": [
{
"reference": "benchmark de melhorias investeMoz.docx; quadro comparativo 04/10/2026",
"sha256": "065203aacfae766c62cf9c2c272ebacc61ff23bd4ff9e91535939fede87f7617",
"checked_on": "2026-11-10"
},
{
"reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Portal Gapi",
"url": "https://www.gapi.co.mz/",
"checked_on": "2026-10-04"
}
],
"draft": {
"title": "Gapi",
"kind": "institution",
"summary": "Crédito, garantias, capacitação e apoio empresarial. Público/finalidade: MPME , empresas rurais, agronegócio, jovens e mulheres, conforme programa.. Referência informativa, não concessão de financiamento.",
"manager_name": "Gapi",
"source_url": "https://www.gapi.co.mz/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Portal Gapi",
"source_checked_on": "2026-10-04",
"source_status": "identified",
"coverage": "unknown",
"coverage_text": "Presença nacional, com programas de cobertura territorial específica.",
"territory_ids": [],
"sectors_scope": "unknown",
"sectors_text": "Critérios sectoriais por conferir no produto/programa; nenhuma elegibilidade automática.",
"sector_ids": [],
"resource_ids": [],
"criteria": [],
"windows": [],
"funding_needs": [
"working_capital",
"investment",
"guarantee",
"technical_assistance"
],
"institution_key": "gapi",
"institution_nature": "other",
"contacts": [
{
"source_url": "https://www.gapi.co.mz/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Portal Gapi",
"checked_on": "2026-10-04",
"kind": "website",
"value": "https://www.gapi.co.mz/"
}
],
"locations": [],
"products": [
{
"source_url": "https://www.gapi.co.mz/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Portal Gapi",
"checked_on": "2026-10-04",
"code": "gapi-oferta",
"title": "Gapi — Financiamento e investimento de impacto",
"instrument": "Crédito, garantias, capacitação e apoio empresarial.",
"needs": [
"working_capital",
"investment",
"guarantee",
"technical_assistance"
],
"coverage": "unknown",
"coverage_text": "Presença nacional, com programas de cobertura territorial específica.",
"territory_ids": [],
"conditions": "MPME , empresas rurais, agronegócio, jovens e mulheres, conforme programa. Actividade de financiamento publicada. A Gapi declara não usar intermediários para conceder financiamento.",
"amounts": "Variável por linha; avaliação própria.",
"availability": "unknown",
"contact_url": "https://www.gapi.co.mz/"
}
]
}
},
"fecop": {
"edition": "tempo-benchmark-20261004-directory-20261110-v1",
"provenance": [
{
"reference": "benchmark de melhorias investeMoz.docx; quadro comparativo 04/10/2026",
"sha256": "065203aacfae766c62cf9c2c272ebacc61ff23bd4ff9e91535939fede87f7617",
"checked_on": "2026-11-10"
},
{
"reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Comunicado oficial Millennium bim",
"url": "https://www.millenniumbim.co.mz/media/gusl5uoe/20260624-comunicado-de-imprensa_millennium-bim-reforca-solucoes-de-financiamento-para-as-pme-atraves-do-fecop.pdf",
"checked_on": "2026-11-10"
}
],
"draft": {
"title": "FECOP — crédito por bancos aderentes",
"kind": "fund",
"summary": "Partilha de risco, crédito e assistência técnica. Público/finalidade: MPME e cooperativas de produtores elegíveis.. Referência informativa, não concessão de financiamento. Condições e montantes no quadro de 04/10/2026: Dotação global anunciada de cerca de €17 milhões; condições por banco e operação. Operacionalização confirmada em Junho de 2026. O Millennium bim mantém comunicado oficial; critérios e canal exacto devem ser confirmados em cada banco.",
"manager_name": "Entidades do programa identificadas na fonte",
"source_url": "https://www.millenniumbim.co.mz/media/gusl5uoe/20260624-comunicado-de-imprensa_millennium-bim-reforca-solucoes-de-financiamento-para-as-pme-atraves-do-fecop.pdf",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Comunicado oficial Millennium bim",
"source_checked_on": "2026-11-10",
"source_status": "identified",
"coverage": "national",
"coverage_text": "Moçambique; operacionalizado por Millennium bim , BCI e Vista Bank .",
"territory_ids": [],
"sectors_scope": "unknown",
"sectors_text": "Critérios sectoriais por conferir no produto/programa; nenhuma elegibilidade automática.",
"sector_ids": [],
"resource_ids": [],
"criteria": [],
"windows": [],
"funding_needs": [
"investment",
"working_capital",
"technical_assistance"
]
}
},
"semeia": {
"edition": "tempo-benchmark-20261004-directory-20261110-v1",
"provenance": [
{
"reference": "benchmark de melhorias investeMoz.docx; quadro comparativo 04/10/2026",
"sha256": "065203aacfae766c62cf9c2c272ebacc61ff23bd4ff9e91535939fede87f7617",
"checked_on": "2026-11-10"
},
{
"reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Semeia Negócios",
"url": "https://semeia.gapi.co.mz/",
"checked_on": "2026-11-10"
}
],
"draft": {
"title": "Semeia Negócios — FECOP",
"kind": "programme",
"summary": "Subvenção em espécie, formalização, capacitação e equipamento produtivo. Público/finalidade: Startups , micro e pequenas empresas; foco em jovens e mulheres.. Referência informativa, não concessão de financiamento. Condições e montantes no quadro de 04/10/2026: Até 450.000 MT; até 75% do investimento; subvenção sem juro. Candidaturas encerradas. O portal permite consultar candidaturas já submetidas.",
"manager_name": "Gapi / FECOP",
"source_url": "https://semeia.gapi.co.mz/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Semeia Negócios",
"source_checked_on": "2026-11-10",
"source_status": "identified",
"coverage": "national",
"coverage_text": "Moçambique; província e distrito constam da candidatura.",
"territory_ids": [],
"sectors_scope": "unknown",
"sectors_text": "Critérios sectoriais por conferir no produto/programa; nenhuma elegibilidade automática.",
"sector_ids": [],
"resource_ids": [],
"criteria": [],
"windows": [
{
"code": "quadro-20261004",
"title": "Chamada indicada no quadro fornecido — encerrada",
"mode": "closed",
"coverage": "programme",
"territory_ids": [],
"source_url": "https://semeia.gapi.co.mz/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; Semeia Negócios",
"checked_on": "2026-11-10",
"application_url": "https://semeia.gapi.co.mz/",
"criteria": []
}
],
"funding_needs": [
"investment",
"technical_assistance"
]
}
},
"fcid-viii": {
"edition": "tempo-benchmark-20261004-directory-20261110-v1",
"provenance": [
{
"reference": "benchmark de melhorias investeMoz.docx; quadro comparativo 04/10/2026",
"sha256": "065203aacfae766c62cf9c2c272ebacc61ff23bd4ff9e91535939fede87f7617",
"checked_on": "2026-11-10"
},
{
"reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; VIII Edição FCID",
"url": "https://fundocatalitico.gov.mz/viii-edicao-fcid/",
"checked_on": "2026-11-10"
}
],
"draft": {
"title": "FCID VIII — Jobs Now",
"kind": "fund",
"summary": "Subvenção comparticipada para expansão produtiva, cadeias de valor e emprego. Público/finalidade: Pequenas e médias empresas e grandes empresas âncora formalizadas.. Referência informativa, não concessão de financiamento. Condições e montantes no quadro de 04/10/2026: Pequenas: USD 50.000–150.000; médias: USD 151.000–250.000; âncora: USD 251.000–500.000. Activo na data da verificação, com prazo iminente. O portal oficial mostrava candidaturas abertas e contagem decrescente; exige validação diária até ao encerramento.",
"manager_name": "Entidades do programa identificadas na fonte",
"source_url": "https://fundocatalitico.gov.mz/viii-edicao-fcid/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; VIII Edição FCID",
"source_checked_on": "2026-11-10",
"source_status": "identified",
"coverage": "territorial",
"coverage_text": "Províncias de Maputo, Gaza, Inhambane, Manica, Sofala e Zambézia. Não pressupõe cobertura nacional. Disponibilidade actual da janela por confirmar; a página indica candidaturas sem prazo final inequívoco.",
"territory_ids": [],
"sectors_scope": "unknown",
"sectors_text": "Critérios sectoriais por conferir no produto/programa; nenhuma elegibilidade automática.",
"sector_ids": [],
"resource_ids": [],
"criteria": [],
"windows": [],
"funding_needs": [
"investment"
]
},
"territory_names": [
"Maputo Província",
"Gaza",
"Inhambane",
"Manica",
"Sofala",
"Zambezia"
]
},
"fae": {
"edition": "tempo-benchmark-20261004-directory-20261110-v1",
"provenance": [
{
"reference": "benchmark de melhorias investeMoz.docx; quadro comparativo 04/10/2026",
"sha256": "065203aacfae766c62cf9c2c272ebacc61ff23bd4ff9e91535939fede87f7617",
"checked_on": "2026-11-10"
},
{
"reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; FAE",
"url": "https://www.bni.co.mz/banca-de-desenvolvimento/fae/",
"checked_on": "2026-11-10"
}
],
"draft": {
"title": "FAE — BNI /Agência do Zambeze",
"kind": "programme",
"summary": "Crédito em desembolso único, tranches ou espécie para agronegócio e empreendedorismo. Público/finalidade: Recém-graduados, MPME , cooperativas e associações lucrativas elegíveis.. Referência informativa, não concessão de financiamento. Condições e montantes no quadro de 04/10/2026: Segundo necessidade real do ciclo de produção e comercialização. Linha e processo publicados. A página não indica uma janela corrente com prazo; marcar “confirmação necessária” antes de aceitar candidatura.",
"manager_name": "BNI / Agência do Zambeze",
"source_url": "https://www.bni.co.mz/banca-de-desenvolvimento/fae/",
"source_reference": "Fonte institucional indicada no quadro Tempo de 04/10/2026; FAE",
"source_checked_on": "2026-11-10",
"source_status": "identified",
"coverage": "unknown",
"coverage_text": "Vale do Zambeze; projectos e actividades elegíveis na região.",
"territory_ids": [],
"sectors_scope": "unknown",
"sectors_text": "Critérios sectoriais por conferir no produto/programa; nenhuma elegibilidade automática.",
"sector_ids": [],
"resource_ids": [],
"criteria": [],
"windows": [],
"funding_needs": [
"investment",
"working_capital"
]
}
}
}
}
@@ -1,5 +1,405 @@
## 11/11/2026 — apresentação pública profissional do directório financeiro em QA
**Pedido do proprietário:** descrições profissionais e apelativas,com descrição/produtos/serviços úteis ao investidor,sem texto de fichas,importação ou documento gerador;orientação transversal registada emAGENTS. O directório e o detalhe passam a uma apresentação editorial comum:reconhece exclusivamente o resumo genérico exacto da importação e usa a natureza publicada da entidade;descrições editoriais específicas são preservadas e limpas da fórmula genérica final. Produtos identificados são apresentados pelos nomes/instrumentos existentes;os sete catálogos específicos disponíveis não são extrapolados para outras entidades. Nas restantes,apresentam-se áreas de serviço conhecidas ou contacto para conhecer soluções,sem inventar produtos/condições.
Retirados da apresentação pública nomes de ficheiros,hashes,linhas de importação,códigos de sucessora e descrições de registo interno. Ligações institucionais e datas úteis continuam acessíveis;metadados e fontes permanecem intactos no cadastro/editor. Instituições históricas/fundidas continuam identificadas como tal. Instituições sem chamada não mostram uma secção vazia de candidatura. Textos da selecção guardada e chamadas públicas dePorque investir/Serviços ajustados à linguagem do investidor. Fluxos de interesse,dossier,edição,revisão/publicação e filtros preservados.
**QA:** nove verificações afectadas aprovadas,três novas incluindo270referências fornecidas,descrições específicas preservadas,produtos não inventados e renderização dos componentes públicos reais de lista/detalhe. Sem escrita em dados activos ou nova publicação do lote;é uma projecção editorial dos dados já publicados. Evidência `outputs/funding-public-editorial-20261111/verification.json` e quatroHTMLs;isto não constitui conferência visual no navegador. Tipos/lint/compilação aprovados;pacote Git com aplicação/reversão conferidas no fecho.
**Estado:** implementado em QA,não instalado;homologação mantém `8f295f05` e270instituições publicadas. Restam instalação/visual e enriquecimento de produtos específicos quando houver informação disponível. O saldo dos outros módulos e resíduos fiscais permanece inalterado. Não é necessário seleccionar ou publicar novamente as270instituições para esta alteração de apresentação.
## 11/11/2026 — rectificação do volume PEC instalada em8f295f05
Instalação expressamente autorizada,executada pelo processo Git existente e conferida às11:03:24Africa/Johannesburg(09:03:24UTC): `8f295f05510c786afeb8ea1dfa692e08d90a0d30`. No simulador fiscal,L8,**Rectificar volume na simulação PEC** conserva o valor original e o corrigido,com escolha explícita/fonte e efeito na estimativa apresentados no relatório. A hipótese de tesouraria anterior exige reconciliação se o total mudar;nenhum comprovativo pago,dedução ou saldo oficial é duplicado. Não executa pagamentos nem submete declaração rectificativa.
Backup novo/restauro isolado conferidos:190tabelas/369ficheiros. Quinze fontes por hash;revisão,entrada,up,login,Concursos,fundos e simuladorHTTP200. Configuração intacta,zero migrações/importações. Totais/checksums idênticos ao backup nas sete tabelas protegidas:funding_programmes,funding_programme_versions,funding_programme_interests,desk_cases,simulation_form_drafts,labour_demands,training_capacities.275fichas/270instituições publicadas preservadas. Recibo `outputs/irpc-pec-correction-20261111/installation.json`;pacote `outputs/entrega-pec-rectificacao-20261111.zip`,SHA256 `559e4477059b092a193d990f7dfb6cb50007d43c24ec00e2484326f076e0c973`. Revisãoa708f079 preservada para reversão.
Este recorte29(3) deixa de ser pendência de implementação/instalação. Visual permanece separado;correcções fora do recorte têm âmbito próprio. Restam última base29(5),PPC excedente,períodos/regimes especiais e demais resíduos L1–L7/restanteL8/L9–L16 e módulos. Próximo trabalho:primeiro residual de simulação executável com as fontes aceites,sem reabrir este recorte. Portal não declarado integralmente concluído.
## 11/11/2026 — rectificação do volume PEC na simulação em QA
**Recorte29(3) avançado:** fonte documental permite rectificar o volume de negócios2025 usado na estimativa PEC2026. Valor original,valor corrigido,titular,exercício e fundamento ficam conservados;escolha explícita entre aplicar a correcção e manter o original. Não submete declaração rectificativa. A estimativa recalcula0,5%,limites e PPC anteriores uma única vez;relatório mostra origem,valor corrigido e efeito na estimativa. Mudança da base/titular/exercício exige reconferência;fonte ou valor ausente não é zero.
Cálculo→API privada→recuperação→HTML/relatório do mesmo dossier em cinco cenários:aumento40.000→90.000MZN,redução40.000→20.000,original conservado40.000,base divergente e correcção para volumezero indeterminados. Hipótese de caixa anterior40.000 deixa de reconciliar quando o total muda;não é sobrescrita. Saldo anual138MZN,comprovativos pagos e histórico anterior preservados. QA55441 existente:uma prova nativa/47asserções com rollback,cinco testes novos de cálculo/interface e40afectados aprovados;tipos/lint/compilação aprovados. Provas `outputs/irpc-pec-correction-20261111/*-verification.json`;pacote Git com aplicação/reversão conferidas no fecho. Não é conferência visual.
**Estado:** implementado em QA,ainda não instalado;homologação mantém `a708f079` e270instituições publicadas. Fonte:RIRPC,Decreto9/2008,art29(3),BR16/I16-04-2008,original já aceite SHA256 `fc373ff5637edf75ec6a9595dec8763a8503d4ee786e3b7d8f1c742eae9d80ee`. Sem migração,base,tabela ou direito novo. Restam instalação/visual,última base29(5),PPC excedente,períodos/regimes especiais e demais resíduos fiscais L1–L7/restanteL8/L9–L16. Correcções fora deste recorte conservam âmbito próprio;L8 e portal não são declarados integralmente concluídos. Próximo trabalho:primeiro residual de simulação executável pelas fontes aceites,sem reabrir este recorte.
## 11/11/2026 — finalidade de viabilidade e projecção PEC instaladas ema708f079
Instalação expressamente autorizada,executada pelo processo Git existente e conferida às10:45:50Africa/Johannesburg(08:45:50UTC): `a708f079b318a13100644c27287f1caa854ecf1d`. No simulador fiscal,L8,os ecrãs apresentam **Simular PPC/PEC**,estimativas fiscais e hipóteses de tesouraria para avaliar a viabilidade do negócio. A hipótese PEC permite distribuir valores por três meses ou concentrar a estimativa num mês escolhido,com soma conferida e prazo legal não certificado. Rascunho,recuperação,histórico e relatórios preservam escolhas;dados documentais já pagos permanecem distintos. O portal não executa pagamentos,nem submete declarações fiscais.
Backup novo/restauro isolado conferidos:190tabelas/369ficheiros. Vinte e quatro fontes por hash;revisão,entrada,up,login,Concursos,fundos e simuladorHTTP200. Configuração intacta,zero migrações/importações. Totais/checksums idênticos ao backup nas sete tabelas protegidas:funding_programmes,funding_programme_versions,funding_programme_interests,desk_cases,simulation_form_drafts,labour_demands,training_capacities.275fichas/270instituições publicadas preservadas. Recibo `outputs/irpc-pec-plan-20261111/installation.json`;pacote `outputs/entrega-simulacao-viabilidade-20261111.zip`,SHA256 `a4dbbe26ca72230e3ff7a5c2a744c187a93380a4857e522b1db5ee17acce3ca5`. Revisãobcbbe51c preservada para reversão.
Este delta deixa de ser pendência de implementação/instalação. Conferência visual permanece separada. Execução de pagamentos não é critério de conclusão deste simulador,conforme clarificação expressa registada emAGENTS. Próximo trabalho:primeiro residual de simulação executável pelas fontes aceites. Base29(5),rectificação29(3),PPC excedente,períodos/regimes e restantes resíduos L1–L7/restanteL8/L9–L16 e módulos mantêm o saldo;portal não declarado integralmente concluído.
## 11/11/2026 — finalidade do simulador e projecção de tesouraria PEC
**Clarificação expressa do proprietário:** o portal simula impostos para avaliar a viabilidade do negócio; não faz pagamentos. A apresentação anterior de «obrigação» e «plano de pagamento» criava uma expectativa errada de execução. Ecrãs PPC/PEC passam a **Simular PPC/PEC**,estimativa fiscal e projecção de tesouraria. Fontes legais e dados documentais já pagos continuam a informar o cálculo; não são operações executadas pelo portal. A execução de pagamentos não é pendência deste simulador.
**Implementado em QA:** hipótese PEC distribuída por Junho/Agosto/Outubro com montantes explícitos cuja soma corresponde exactamente ao total,ou hipótese concentrada num mês escolhido. Não inventa igualdade/arredondamento ou validação de prazo para a opção única. Valores desconhecidos,divergência da soma,total alterado e ausência de estimativa positiva invalidam a projecção dependente,sem bloquear o cálculo fiscal. Trocar hipótese conserva edições;resumo com Alterar. Rascunho privado,recuperação,histórico e relatório do mesmo dossier reutilizados. As estimativas não alimentam os comprovativos pagos nem alteram deduções anuais.
Cinco verificações novas de cálculo/interface e35afectadas aprovadas;percurso sintéticoAPI/recuperação/relatório em quatro cenários(distribuído,concentrado,divergente,total alterado),com saldo anual138MZN conservado. Provas `outputs/irpc-pec-plan-20261111/*-verification.json`. Sem migração,base,papel ou permissão nova. Tipos/lint/compilação aprovados;uma prova nativa/38asserções com rollback. Pacote Git e reversão conferidos no fecho. Isto não é conferência visual.
**Estado:** QA,ainda sem instalação;homologação permanece `bcbbe51c` e270instituições publicadas. Restam instalação/visual e recortes fiscais de base29(5),rectificação29(3),PPC excedente,períodos/regimes e outras regras de simulação;prazo legal da hipótese única não é certificado. L1–L7/restanteL8/L9–L16 e demais módulos permanecem no saldo,sem transformar o portal em sistema de execução fiscal. Próximo trabalho:primeiro residual de simulação executável com fontes aceites.
## 11/11/2026 — total e calendário PEC instalados embcbbe51c
Instalação expressamente autorizada,executada pelo processo Git existente e conferida às10:28:18Africa/Johannesburg(08:28:18UTC): `bcbbe51c2fc279d5b2d85412f7bcb6ca2423a9b6`. Utilizável no simulador fiscal,fecho anual L8: **Preparar obrigação PEC**,com total ordinário2026 baseado no volume documental2025,limites antes dos PPC pagos anteriores,mesesJunho/Agosto/Outubro e dispensa no ano de início efectivo. Guardar/recuperar pelo rascunho privado e apresentar no relatório do mesmo dossier. Não cria pagamento,dedução anual ou reembolso.
Backup novo/restauro isolado conferidos:190tabelas/369ficheiros. Catorze fontes por hash;revisão,entrada,up,login,Concursos,fundos e simuladorHTTP200. Configuração preservada,zero migrações/importações. Sete tabelas protegidas mantêm totais/checksums do backup:funding_programmes,funding_programme_versions,funding_programme_interests,desk_cases,simulation_form_drafts,labour_demands,training_capacities.275fichas/270instituições publicadas preservadas. Recibo `outputs/irpc-pec-obligation-20261111/installation.json`;pacote preservado `outputs/entrega-pec-obrigacao-revisada-20261111.zip`,SHA256 `a9d9bb089305003487695969d653c7f876a913fce44bbbffb5191c1a792a9184`. Revisãofbff2660 preservada para reversão.
Este recorte deixa de ser pendência de implementação/instalação;visual permanece separado. Restam montantes por prestação/pagamento único,última base29(5),rectificação29(3),PPC excedente,períodos/regimes especiais,guias/juros. Próximo trabalho:primeiro desses casos executável pelas fontes aceites,sem repetir o recorte instalado. L1–L7/restanteL8/L9–L16 e restantes módulos continuam no saldo;portal não declarado integralmente concluído.
## 11/11/2026 — total e calendário PEC ordinário em QA
**Novo recorte L8:** preparação do total PEC2026 pelo volume de negócios documental2025:0,5%,limites30.000/100.000MZN aplicados antes de deduzir PPC efectivamente pagos no ano anterior. MesesJunho/Agosto/Outubro,sem inventar dia limite ou importar o arredondamento PPC. Dispensa do ano de início efectivo da actividade29(4) fundamentada separadamente. Fonte:RIRPC,Decreto9/2008,art29(1–5),BR16/I16-04-2008,original SHA256 `fc373ff5637edf75ec6a9595dec8763a8503d4ee786e3b7d8f1c742eae9d80ee`, já preservado no acervo.
Formulário progressivo reutiliza titular/exercício e conserva valores ao ocultar campos sem aplicação; rascunho privado e relatório do mesmo dossier reutilizados. Total preparado não é PEC pago,nem cria dedução anual,reembolso,guia ou consumo de saldo oficial. Ausência de informação não equivale a zero. Sem volume no ano anterior exige base29(5);PPC superiores ao montante limitado ficam por reconciliar,sem obrigação negativa. Esses casos não impedem o cálculo anual independente.
**Percurso sintético:** cálculo→API privadaPOST/PUT→GET→recálculo→HTML/relatório:40.000MZN ordinário,20.000mínimo antes de PPC,90.000máximo antes de PPC,dispensa inicial0,volumezero e desconhecido indeterminados. Histórico e saldo anual138MZN preservados. QA55441 existente,uma prova nativa/56asserções com rollback,sem nova base/tabela/porta/papel/migração. Seis verificações novas de cálculo/interface e29afectadas aprovadas; tipos/lint/compilação aprovados. Provas `outputs/irpc-pec-obligation-20261111/*-verification.json`;pacote Git com aplicação/reversão conferidas no fecho.
**Saldo:** implementado em QA,ainda não instalado. Homologação mantém `fbff2660` e270instituições publicadas. Restam entrega Git e visual;montantes por prestação/opção de pagamento único,última base29(5),rectificação29(3),PPC acima do limite,períodos/regimes especiais,guias/juros permanecem abertos. L8 não é integralmente concluído;resíduos L1–L7/restanteL8/L9–L16 e restantes módulos conservados. Próximo trabalho:primeiro desses casos executável com as fontes aceites,sem reabrir os recortes entregues.
## 11/11/2026 — obrigação PPC instalada emfbff2660
Instalação expressamente autorizada e executada pelo processo Git existente: `fbff2660f280f53fe079221164bb21917032ac70`, conferida às09:04:59Africa/Johannesburg(07:04:59UTC). Utilizável no simulador fiscal,fecho anual L8: **Preparar obrigação PPC**,com imposto anterior documental líquido de retenções,80%,três prestações arredondadas individualmente por excesso ao milhar e calendárioMaio/Julho/Setembro. Guarda e recuperação pelo rascunho privado existente e inclusão no relatório do dossier. Obrigação preparada não equivale a pagamento,nem cria dedução de PPC.
Backup novo/restauro isolado conferidos:190tabelas/369ficheiros. Catorze fontes do delta por hash e revisão,entrada,up,login,Concursos,fundos e simuladorHTTP200. Configuração preservada,zero migrações/importações;sete tabelas protegidas com totais/checksums idênticos ao backup:funding_programmes,funding_programme_versions,funding_programme_interests,desk_cases,simulation_form_drafts,labour_demands,training_capacities.275fichas/270instituições publicadas preservadas. Recibo `outputs/irpc-ppc-obligation-20261111/installation.json`; pacote `outputs/entrega-ppc-obrigacao-20261111.zip`,SHA256 `ab72a11b9306e4503ab92669c47ec592b2510aa01189ebab1d5216bb40d3769a`. Revisão6805b606 preservada para reversão.
Este recorte deixa de ser pendência de implementação/instalação. Conferência visual permanece separada. Próximo residual fiscal:PEC/obrigações e outros casos executáveis com fontes aceites;anualização/períodos especiais,dispensas/guias/juros e resíduos L1–L7/restanteL8/L9–L16 mantêm-se explícitos. Restantes módulos permanecem no saldo existente;portal não declarado integralmente concluído.
## 11/11/2026 — obrigação PPC ordinária de L8 em QA
**Pendência avançada:** o fecho anual existente passa a preparar a obrigação PPC2026 com liquidação documental do exercício civil completo2025: imposto anterior21(1) líquido de retenções,80%,três prestações iguais arredondadas individualmente por excesso ao milhar de meticais. Calendário por mês:Maio,Julho,Setembro,sem inventar dia limite. Fonte:RIRPC,Decreto9/2008,arts27(1)(a)/28(1–3),BR16/I16-04-2008,original SHA256 `fc373ff5637edf75ec6a9595dec8763a8503d4ee786e3b7d8f1c742eae9d80ee`, já preservado no acervo. Não usa colecta projectada corrente como liquidação anterior.
Reutiliza titular/exercício e rascunho privado existentes; formulário progressivo conserva edições deliberadas ao trocar enquadramento. Obrigação preparada e comprovativos efectivamente pagos permanecem distintos: não cria pagamento,dedução,saldo oficial,guia ou juros. Campos vazios,fonte ausente,divergência de titular/período,base negativa e período especial ficam indeterminados,sem impedir o fecho anual independente. Zero explícito permanece zero. Anualização28(3),dispensas,PEC e restantes regimes ficam separados.
**Prova sintética:** cálculo→POST/PUT→GET privado→recálculo→HTML/relatório do mesmo dossier em cinco cenários:72.000MZN ordinário,75.000após arredondamento,zero,valor desconhecido e período especial indeterminados. Saldo anual138MZN e comprovativos inalterados; histórico anterior conservado. Uma prova nativa/47asserções com rollback no QA55441 existente; cinco verificações novas de cálculo/interface e24afectadas aprovadas. Tipos/lint/compilação aprovados; pacote Git com aplicação/reversão conferidas no fecho. Provas `outputs/irpc-ppc-obligation-20261111/*-verification.json`.
**Estado:** implementado em QA, ainda sem instalação. Homologação mantém `6805b606`,Educação entregue e270instituições publicadas. Restam entrega Git deste delta e visual; L8 não está integralmente concluído. Preservam-se os resíduos L1–L7,restanteL8/L9–L16 e restantes módulos. Próximo trabalho fiscal: PEC/obrigações e demais casos executáveis com as fontes aceites,sem reabrir PPC ordinário.
## 11/11/2026 — retoma da revisão de Educação instalada em6805b606
Instalação autorizada pelo utilizador, executada pelo processo Git existente e conferida às08:46:05Africa/Johannesburg(06:46:05UTC): `6805b606a106fbed410b191ed778c0fad7c8e913`. Guardar e recuperar a preparação privada da revisão interna de procura/capacidade fica utilizável na homologação, pelo detalhe existente em `/management/education`. A gravação privada não decide nem aprova; preserva notas, versão analisada e a chave de uma intenção incerta.
Backup novo e restauro isolado conferidos:190tabelas/369ficheiros. Catorze fontes conferidas por hash; revisão,entrada,up,login,Concursos,fundos e simuladorHTTP200. Configuração intacta, zero migrações/importações. Totais e checksums inalterados nas sete tabelas protegidas:funding_programmes,funding_programme_versions,funding_programme_interests,desk_cases,simulation_form_drafts,labour_demands,training_capacities. Permanecem275fichas/270instituições publicadas. Recibo `outputs/education-review-preparation-20261111/installation.json`; pacote preservado `outputs/entrega-educacao-retoma-20261111.zip`, SHA256 `d2b7fd5e0d24baffd7b53d534c78e6e0f4492b47a40bb1d9d146b8a8b553caf5`.
Este recorte deixa de ser pendência de implementação e instalação. Conferência visual continua pendente; jornadas externas educativas e representação/consentimento documentados permanecem em aberto. Resíduos fiscais L1–L7/restanteL8/L9–L16 e restantes módulos mantêm-se no saldo existente. Próximo trabalho: primeiro residual executável dessa ordem, sem repetir este recorte nem as provas aceites.
## 11/11/2026 — retoma durável da revisão de Educação em QA
A revisão interna de procura e capacidade pode guardar e recuperar a preparação privada, incluindo notas, versão analisada e intenção de decisão incerta. Reutiliza os eventos de auditoria existentes, com conteúdo cifrado e isolamento por conta, tipo e declaração. Guardar não decide: a decisão continua pelo escritor nativo, com revisão independente e validação de versão. A intenção fica persistida antes do envio; uma resposta perdida recupera a mesma chave, sem duplicar a decisão. Edições posteriores e preparações mais recentes noutra janela não são substituídas silenciosamente.
**QA concluído, ainda não instalado:** cinco testes nativos/105 asserções e dez testes novos de interface, com os 17 afectados preservados; análise PHP e tipos aprovados. A página nativa recuperada foi exercitada no componente real em harness: recibo reconhecido, edição posterior conservada e zero envios adicionais. Prova `outputs/education-review-preparation-20261111/verification.json`. Isto não constitui conferência visual no navegador. Sem migração, tabela, papel ou concessão nova.
**Saldo:** falta entregar este delta pelo processo Git e conferir a apresentação no uso normal. Jornadas externas de estudantes, formadoras e empregadores, representação e consentimento documentados permanecem pendentes; Educação não é declarada integralmente concluída. A homologação mantém `9c067d6a` e as 270 instituições publicadas. Permanecem explícitos os resíduos fiscais L1–L7/restante L8/L9–L16 e os restantes módulos. As provas aceites não foram reabertas.
## 11/11/2026 — combinação CBF15/créditos/PEC instalada em9c067d6a
Instalação expressamente autorizada nesta retoma e executada pelo processoGit existente: `9c067d6ae89c0cd1e9ad4f2813e84048988d3319`, conferida às07:43:38Africa/Johannesburg(05:43:38UTC). Dezficheiros do delta conferidos por hash; revisão,entrada,login,up,Concursos,fundos e simuladorHTTP200; configuração preservada, zero migrações/importações. Backup/restauro isolado conferido:190tabelas e369ficheiros. Nenhum restauro sobre dados activos.
**Utilizável na homologação:** combinação CBF15corrente/transportado com créditos económicos/internacionais e saldos anteriores, com escolha dePEC limitada à colecta residual apósCBF15; gravação/recuperação e relatórios pelo percurso existente, conforme cinco cenáriosQA aceites. Os recortes anteriores de transporte e os históricos são preservados. Nenhum saldo oficial é consumido, pagamento/reembolso executado ou nova prioridade entre exercícios inventada.
**Preservação activa comprovada:** totais e checksums idênticos ao backup nas tabelas `funding_programmes`, `funding_programme_versions`, `funding_programme_interests`, `desk_cases` e `simulation_form_drafts`.275fichas,270publicadas; o original financeiro continua disponível. Não se extrapola esta comparação para tabelas voláteis de sessão/cache. Prova `outputs/irpc-cbf15-combination-20261111/installation.json`; pacote e reversão `outputs/entrega-irpc-combinacao-20261111.zip`, SHA256 `cebcd063b06f70a33b57f6cdd710478fb7aa3d18ef64aec4489cf855064bff99`.
Saldo: este recorte deixa de ser pendência de implementação/instalação. Conferência visual mantém-se separada; continuam repartição automática/múltiplos projectos, outros períodos/moedas/regimes/EP/cumulações/benefícios/formulários e os restantes resíduos L1–L7/restanteL8/L9–L16 e módulos não fiscais. O portal não é declarado integralmente concluído. A próxima implementação seguirá o primeiro residual executável do saldo existente, sem reabrir a combinação entregue nem a publicação das270instituições.
## 11/11/2026 — combinação CBF15 com créditos económicos/internacionais e PEC em QA
Retoma da ordem de conclusão existente, sem nova auditoria ou repetição da publicação financeira:270instituições publicadas e revisão instalada4f3dbcf8 preservadas; visual financeiro/S08 permanece separado. Reconciliação delimitada: o transporte CBF15 básico(7476dc34) já integra a revisão instalada4f3dbcf8, pelo histórico Git e recibos aceites; a nota antiga «QA, não instalado» desse transporte foi superada por entregas posteriores. Não reinstalar esse recorte como se estivesse ausente.
**Implementado nesta passagem, ainda sem instalação:** retirada a recusa técnica genérica da combinação de CBF15 corrente/transportado com saldos anteriores, reutilizando as fontes e ordem de dedução existentes. Créditos económicos e internacionais/correspondentes transportes precedemCBF15; PEC corrente e anterior usam apenas a colecta residual seguinte, antes de retenções/PPC. Uma escolha explícita de PEC superior ao residual é recusada como indeterminada, sem reduzir silenciosamente parcelas ou fabricar saldo recuperável. Histórico e fontes de investimento/uso anterior continuam preservados; a chave da combinação inclui versão própria para exigir recálculo dos resultados anteriores desse ramo, sem reescrever históricos nem invalidar os recortes sem combinação.
Cinco cenários sintéticos: combinação ordinária138MZN; quota residual documental100→saldo438; PEC sobrealocado→saldo indeterminado; CBF15 esgota colecta1338 ePEC seleccionado expressamente0→saldo−500 proveniente dePPC, sem reembolso/consumo oficial; janelaCBF15 expirada→saldo538. Também provada a varianteCBF15 corrente e o limite de quota após créditos anteriores. Cálculo→POST/PUT do rascunho privado existente→GET→recálculo exacto→HTML/dossier aprovados no QA55441/proactive_investment_20261027_test, com rollback e sem novas bases/portas/migrations/direitos. Uma provaAPI/37asserções; cinco testes novos nas combinações e23casos afectados de compatibilidade aprovados, não multiplicados como funcionalidades. Tipos/lint aprovados; compilação QA e pacote registados no fecho. Provas `outputs/irpc-cbf15-combination-20261111/*-verification.json` e relatórios recuperados.
**Saldo:** implementação e percurso desta combinação concluídos emQA; instalação do novo delta e visual separados. Persistem repartição automática/múltiplos projectos, outros períodos/moedas/regimes/EP/cumulações/benefícios e documentos fora dos recortes demonstrados; L1–L7/restanteL8/L9–L16 não são encerrados. Os cinco programas anteriores privados do directório financeiro não são publicados por consequência desta entrega. Próximo trabalho: agrupar a entrega compatível deste delta e continuar o primeiro residual fiscal executável, preservando os fechos aceites.
## 10/11/2026 —270instituições publicadas e consultáveis na homologação
**Cadastro e disponibilização do documento fornecido concluídos em homologação.** Conferência de leitura às22:54:31Africa/Johannesburg(20:54:31UTC), revisão instalada `4f3dbcf86499180011fc79712d6b5be445e57e26`:270/270instituições/operadores do documento publicados, nenhuma referência do original em falta,270identidades únicas,393referências físicas e168contactos. Publicações confirmadas pelo fluxo editorial nativo do utilizador; zero escritas efectuadas pelo verificador. As275fichas totais incluem cinco referências anteriores de fundos/programas que permanecem privadas, sem serem publicadas por consequência deste lote. Fonte original/SHA/edição, incertezas e identidades preservadas.
Consulta pública comprovada porHTTP200 e dados efectivamente devolvidos: `/fundos?kind=institution&include_historical=1` contém270resultados; filtro categoria seguros/tipo seguradora/presença física/Nampula contém seis(Fidelidade,Emose,HollardMoçambique,Britam,Arko,Palma). Fichas deBCI,Absa eArko abrem no componente público e conservam a fonte SHA256 `119bb5514d2777849b70aebfec21d28a41885cbdc7d9827fbebd764fe994c108`. A opção de incluir histórico não transforma uma instituição fundida/extinta em activa. Sede/balcões continuam separados de cobertura e produtos; dados desconhecidos não se tornam condições confirmadas nem garantias de financiamento/seguro.
**Saldo ACT-20261029-12 neste recorte:** conteúdo do documento cadastrado, publicado pelo editor e disponível por localização/categoria/tipo; já não é página vazia ou apenas códigoQA. Continuidade banco/seguradora/balcão/fonte/versão no mesmo dossier, gravação/recuperação e conservação após alterações continuam cobertas pelas provasQA aceites, reutilizadas sem criar dossier sintético em produção. Conferência visual de navegador permanece separada; não se presume instalação de outros módulos, fecho integral do portal ou conclusão dos resíduos fiscais. Nenhuma nova instalação/alteração de direitos exigida para declarar este resultado. Prova `outputs/funding-directory-20261110/institutions/publication-complete.json`.
## 10/11/2026 — recusa depois do login: navegadores e sessões diferentes
URL comunicado pelo utilizador:`/dashboard`. Conferência dirigida dos registos confirma login/MFA no **Edge** às22:03:53, seguido de`/dashboard`HTTP200 às22:03:55. A abertura seguinte de`/management/fundos/referencias-assistidas`às22:04:02/22:04:17 recebeu403 em **Safari/WebKit**, não no Edge. Existiam duas sessões da mesma conta: a sessão recente do Edge com confirmaçãoMFA válida e a anterior com confirmação antiga. A autorização editorial/verificação de email/perfil existentes estavam preservados. Logo esta nova recusa não comprova expiração do MFA recém-concluído no Edge nem falta de permissão da conta; o link abriu outro contexto de navegador.
Solução indicada: copiar e colar o URL do editor na barra do Edge já autenticado, mantendo a mesma aba nas retomas. Não repetir login por rotina, alterar permissões, desactivar verificações ou reinstalar por este motivo. **Reposição de acesso confirmada:** GETdo editor noEdge às22:11:21(+02),HTTP200. A recusa de entrada ficou delimitada e resolvida por uso da sessão correcta, sem nova instalação, concessão ou alteração de segurança. A instalação4f3dbcf8 permanece; publicação/conteúdo consultável ainda não declarados concluídos. Consulta estritamente de leitura; nenhum identificador de sessão/token/cookie/palavra-passe exportado e nenhuma sessão modificada. Prova:`outputs/funding-directory-20261110/institutions/access-context-diagnosis.json`.
## 10/11/2026 — retoma editorial instalada em4f3dbcf8
Correcção instalada pelo executor Git existente em `4f3dbcf86499180011fc79712d6b5be445e57e26`, confirmada às22:01:41Africa/Johannesburg (20:01:41UTC).7ficheiros conferidos por hash; revisão/entrada/login/up/Concursos/fundos HTTP200, configuração preservada, zero migrações/importações automáticas. Novo backup/restauro190tabelas/369ficheiros conferido, sem substituir dados activos. Pacote `outputs/entrega-instituicoes-retoma-20261110.zip`, SHA256 `9bce0250d546cd87f7c60a55f0c1d47fba74e8965f7d3224a588e140f87ec3cb`; anteriores preservados.
**Saldo activo:90rascunhos, zero publicações.** A indicação de publicação não foi confirmada no servidor por expiraçãoMFA/403. A correcção torna visível o estado e conserva os recibos na mesma aba, poractor/fontes; não publica por consequência da instalação nem altera o limiteMFA900s. Os90rascunhos anteriores ao armazenamento no cliente são recuperados idempotentemente na primeira passagem actualizada; após isso as fichas conferidas são saltadas nas retomas da mesma aba. Confirmações de revisão/publicação voltam a ser exigidas na entrada, e o backend revalida guardas/hash/versão. Fechar a aba/limpar armazenamento não elimina rascunhos, mas perde a retoma local. Estado alterado por outra janela exige conferência actual; o armazenamento do cliente não é prova de publicação.
Resta a conclusão autenticada da preparação/publicação e a consulta efectiva do conteúdo no destino. ACT12 continua aberto, visual pendente, sem reabrir APIEX/SWOT/S08 ou resíduos fiscais anteriores. Prova: `outputs/funding-directory-20261110/institutions/resume-installed.json`.
## 10/11/2026 — retoma após MFA, sem repetir fichas conferidas
Verificação activa posterior:90rascunhos/zero publicações; continuação GETMomentum403 às21:49:39 ePOSTpublicar403 às21:49:54 (+02). A sessão voltou a expirar. A publicação comunicada não chegou a ser executada pelo servidor. Nenhum rascunho foi removido.
Correcção delimitada emQA: a mesma aba conserva selecção, recibos/versionamento/hash e fundamento, separados por conta e conjunto de fontes; após reautenticação salta fichas já conferidas. As confirmações de revisão/publicação são desmarcadas na nova entrada, sem publicação automática. Uma publicação de resposta incerta conserva o mesmo corpo para recuperação idempotente. Novo comando de renovação utiliza logout/login/MFA nativos; limite900s e guardas mantidos. «Seleccionar todas as instituições(270)», contagem de progresso e controlos antes da lista recolhida tornam o estado visível. Sem migração, dados novos ou alterações de permissões. Alteração ainda não instalada nesta anotação; revisão activaef24cbdc.
Prova dirigida do componente efectivo: remonta a página como após autenticação, recupera selecção/recibos, não repete preparação, não publica automaticamente, recupera confirmação incerta e isola outra conta/fontes. Prova API editorial anterior1caso/27asserções reexecutada apenas pela propriedadeeditor_id acrescentada; provas270fichas e do dossier reutilizadas. Tipos/análise/compilação da entrega são registados pelo executor existente.
## 10/11/2026 — confirmação da preparação parcial e expiração do MFA
Após execução no editor, a homologação contém **32rascunhos e zero fichas publicadas**. A preparação gravou efectivamente e avançou por diferentes instituições. Os registos HTTP mostram preparação200 atéMyBucks às21:31:42, seguida deGETxtenda403 às21:31:43 ePOSTpublicar403 às21:31:59 (Africa/Johannesburg). A entrada no editor fora confirmada às21:16:43; corresponde ao limite existente de900segundos do MFA em `PortalProfileAccess::mfa`. Não é perda da instalação nem repetição de dados. Reautenticar no portal com MFA e retomar a preparação idempotente conserva os32rascunhos, sem substituir edições; publicação continua a exigir a confirmação separada. Nenhuma guarda, sessão ou permissão foi alterada. Revisão instalada `ef24cbdc`, ACT12 ainda sem conteúdo público confirmado. Prova de leitura: `outputs/funding-directory-20261110/institutions/publication-observation.json`.
## 10/11/2026 — instalação confirmada do directório financeiro
Revisão `ef24cbdc9218e2027f7d1a39968cc60869a35410` instalada na homologação às19:15UTC, pelo executor Git existente.33ficheiros conferidos por hash com o commit; configuração preservada, zero migrações/importações automáticas. Backup/restauro confirmado de190tabelas e369ficheiros. Entradas `/fundos?kind=institution`, `/concursos`, `/login`, `/up` e revisão HTTP200. Pacote anterior preservado; nova entrega `outputs/entrega-instituicoes-editorial-20261110.zip` SHA256 `168d7ffb6d054533674f3f07799959b46bc12641c045accfc1766da5cead9077`.
**Implementação instalada; conteúdo ainda sem publicação confirmada.** Leitura activa:zero fichas/zero publicações; tabelas sem RLS, logo o resultado não decorre de falta de contexto de leitura. Entrada autenticada no editor assistido confirmada nos registos; ainda sem pedidos POST de preparação/publicação observados. A indicação do utilizador de operação concluída permanece por reconciliar com o resultado efectivo, sem repetir a importação ou simular MFA. A conta existente tem designação editorial e perfil exigidos; nenhuma concessão alterada. ACT-20261029-12 permanece aberto até existirem fichas consultáveis. Visual e restantes resíduos do portal mantidos.
## 10/11/2026 — preparação editorial assistida das instituições
A autenticação SSH foi confirmada, mas não substitui a sessão e MFA recentes do editor no portal. Acrescentada `/management/fundos/referencias-assistidas`: selecção explícita das instituições do documento, conferência das fontes, preparação sequencial idempotente e publicação confirmada numa segunda operação. Preparar nunca publica. Fichas deliberadamente alteradas são preservadas e exigem revisão individual; mudança do conjunto de fontes interrompe a operação. Uma resposta de publicação perdida conserva versão/hash/fundamento para a mesma repetição. Expiração de MFA mantém as guardas e os resultados já confirmados.
Prova adicional delimitada:1caso API/27asserções,1caso frontend de separação preparação/publicação e recuperação da confirmação. Reutilizadas as5provas API/735asserções e28frontend anteriores; não se repetiu a carga das270instituições. Instalação e publicação no destino continuam distintas e serão registadas após confirmação efectiva. Sem migrações, novos direitos ou alteração das sessões existentes.
## 10/11/2026 — instituições por localização, categoria e tipo no processo do investidor
Pedido expresso da Tempo: inserir as instituições do documento agora indicado por localização/categoria/tipo e permitir que o investidor consulte bancos, seguradoras e outros intervenientes quando a necessidade surgir, sem limitar a decisão por falta de informação. **Implementação e conteúdo demonstrados em QA; nenhuma instalação ou publicação activa executada nesta passagem. Última revisão instalada confirmada:1c75a30a, APIEX/SWOT/Concursos preservados. ACT-12 não é encerrado pela preparação técnica.**
Original read-only `Notas/Instituições financeiras em Moçambique.docx`, edição Novembro2026, SHA256 `119bb5514d2777849b70aebfec21d28a41885cbdc7d9827fbebd764fe994c108`. Texto,509linhas de tabelas e nomes/contactos presentes nos parágrafos reconciliados. **270instituições/operadores,393referências de localização física e168referências de contacto**, não270ofertas de financiamento nem393moradas independentemente confirmadas. Instituições repetidas são reunidas por identidade explícita/aliases documentados ou número de registo citado; divergências, moradas alternativas, terceiros, dados históricos e identidades por confirmar permanecem identificados, sem substituir factos. Entidades sem morada continuam consultáveis como não localizadas. Totais estatísticos/redes incompletas não criam entidades ou agências fictícias. Listas antigas não confirmam licença actual; ABC permanece fundida/extinta segundo o documento, ligada à referência Access, sem atribuir a origem dos balcões por inferência.
Reutilizado o catálogo/versionamento/editorial existente: categorias banca,crédito/microfinanças,gestão de investimento,pensões,seguros/resseguros,mediação,câmbio,pagamentos e supervisão; uma entidade pode ter mais de uma categoria/função sem duplicação. Filtros por categoria,tipo e presença física são distintos da cobertura do produto. Província do cadastro é associada apenas por correspondência inequívoca; rua/localidade e precisão limitada conservam-se como declaradas. Pesquisa por cidade/balcão/morada/alias permite localizar informação textual (por exemplo Beira), sem geocodificação inventada. Informação por confirmar não é correspondência confirmada; ausência de registo não prova ausência de oferta. Oito gestoras de pensões continuam distintas de produtos de crédito, e seguradora não é apresentada automaticamente como financiadora/garantia de crédito.
**Continuidade no mesmo dossier:** ferramentas do investimento abrem a pesquisa contextual; seleccionam-se necessidade (incluindo seguros), instituição e, opcionalmente, sede/balcão. Guarda-se identidade estável, categorias/tipos, versão pública, local exacto escolhido, fonte/documento/SHA/linha, estado de confirmação e produto quando existente. Banco e seguradora coexistem; gravação repetida não duplica escolha. Actualização posterior dos pressupostos do investimento conserva a escolha; retirada da ficha pública não apaga a fotografia consultada. Dados/etapas/autoridade de decisão anteriores não são alterados. Consulta pública livre, operações privadas e edição editorial com as guardas existentes; nenhuma candidatura, email ou contacto enviado às instituições, novo perfil/grant/porta/schema ou ligação fictícia.
Provas no QA55441 existente, com rollback: **5casos API/735asserções únicas** (2desta fonte/608 e3de compatibilidade/127),28frontend dirigidos incluindo S08/navegação e recuperação da tentativa com balcão escolhido, tipos/PHPStan/lint/Pint/compilação QA aprovados. Uma tentativa de suite paralela foi recusada pela guarda da mesma base; compatibilidade executada sequencialmente sem remover a guarda ou criar outra base. `outputs/funding-directory-20261110/institutions/{content,verification}.json` e **`catalogo-qa.html` com270fichas ligadas**; renderização estática dos componentes, não conferência visual de navegador. Provas e pacote anteriores das11fichas/BNI Capital/FDEL mantidos. As7instituições comuns são enriquecidas pela mesma identidade; não há segundo directório. Fontes/documento original não foram alterados.
**Saldo concreto:** conteúdo e percurso local/categoria/tipo → banco/seguradora → fonte/contacto → mesmo dossier → continuação/recuperação demonstrados em QA. Restam instalação e disponibilização/revisão/publicação autenticada pelo fluxo editorial existente, consulta efectiva no destino e visual (conector indisponível). Preparação documental não publica lote automaticamente, conformeAGENTS.md; não declarar informação já disponível na homologação por existir este código. Restantes módulos e resíduosL1–L7/restanteL8/L9–L16 mantêm o saldo anterior.
### Consulta de bancos, seguradoras e outras instituições durante o processo
No investimento escolha **Instituições financeiras e apoios**; no menu comum há **Instituições financeiras e seguros**. Seleccione categoria/tipo e escolha entre **Sede, balcão ou presença física** e **Cobertura do financiamento/produto**. Use a localização do cadastro e, se necessário, palavras da cidade, balcão ou morada. Confira a fonte e as reservas: “reportado no documento”, “a confirmar”, “histórico”, “identidade por confirmar” e “localização por confirmar” não são equivalentes a confirmação actual de licença/oferta.
Na ficha, seleccione o dossier/oportunidade existentes, a necessidade (Seguros quando procura uma seguradora) e o balcão desejado, se identificado. Guardar conserva uma referência privada; não contrata seguro, não solicita empréstimo nem confirma garantia. No dossier, **Instituições e serviços ligados ao processo** apresenta banco e seguradora separadamente com categoria,tipo,balcão e origem. Os pressupostos/etapas seguintes não apagam essas escolhas; a ficha actual pode mudar mas a versão consultada fica no histórico privado. Não é preciso preencher um pedido externo para consultar os contactos.
## 10/11/2026 — ACT-20261029-12: directório financeiro com conteúdo em QA
**Homologação mantém1c75a30a; este conjunto não está instalado nem publicado.** A leitura autenticada do destino confirmou catálogo /fundos HTTP200, schema instalado e zero fichas/versões. O requisito não fica encerrado por essa disponibilidade técnica. APIEX/SWOT mantêm a entrega instalada própria, sem reabertura.
Localizada e reutilizada a lista fornecida em `Relatorios/benchmark de melhorias investeMoz.docx`, SHA256 `065203aacfae766c62cf9c2c272ebacc61ff23bd4ff9e91535939fede87f7617`, quadro de04/10/2026. Doze entradas documentais correspondem a **11fichas:7instituições e4programas/fundos** — BCI,Millennium bim,Absa,Standard Bank,Moza,BNI,Gapi,FECOP,Semeia Negócios,FCID VIII eFAE; **BNI Capital** integra os produtos do BNI, sem duplicar instituição ou presumir personalidade jurídica autónoma. Agregado genérico de linhas BNI não gera fundos fictícios. Referência FDEL anterior conservada, sem a contar novamente nesta entrega.
O cadastro/versionamento/competência editorial existentes passam a conservar natureza factual, identidade estável, contactos com fonte/data, locais físicos (sede/balcão/ponto institucional) e produtos próprios. Moradas não determinam cobertura financeira. Produto tem finalidade, instrumento, condições/montantes, disponibilidade e cobertura próprios; vazios são identificados como por confirmar. Cobertura e necessidade têm de corresponder ao **mesmo produto**, antes da paginaçãoSQL. Critério documentado pode excluir desconhecidos; consulta geral distingue resultados por confirmar de correspondência sustentada. FCID conserva seis províncias documentadas, sem confundir Maputo Província/Cidade; FAE mantém o Vale do Zambeze como âmbito textual com vínculo geográfico por confirmar, sem inventar distritos. A localização institucional do BNI não limita o financiamento a Maputo. Semeia conserva a chamada encerrada; a antiga linha BCI2018 não é reaberta. Condições e janelas históricas não se tornam ofertas actuais pela importação. Fontes indicadas consultadas para âmbito/disponibilidade; falha de recolha da Gapi não revoga o original, cuja data04/10 e disponibilidade por confirmar ficam explícitas.
Demonstrado em QA55441 existente: localização/necessidade → consulta das fichas reais fornecidas → fonte/contacto/condições → escolha privada no mesmo dossier e oportunidade → recuperação. Snapshot preserva versão pública e produto consultados mesmo após retirada/alteração da ficha. Repetição da intenção e recuperação após recarregar não duplicam a escolha/evento; outra oportunidade/actor e dossier encerrado mantêm guardas. Não envia candidatura, contactos ou dados às instituições, não concede crédito nem classifica o investidor automaticamente. Sem pontuação/IA, schema/base/porta nova, novas competências ou alteração de dados activos.
Provas dirigidas: **3casos API/127asserções e13frontend**, PHPStan nível7 sem supressões, tipos/lint/Pint/compilação QA aprovados. Testes transaccionais revertidos; sequências QA podem avançar. `outputs/funding-directory-20261110/verification.json`, `content-qa.json`, `dossier-qa.json`; **catálogo consultável de revisão** `outputs/funding-directory-20261110/catalogo-qa.html` e11fichasHTML ligadas. Renderização estática dos componentes afectados, não conferência visual de navegador nem publicação no destino. Provas anteriores preservadas e reutilizadas.
**Saldo ACT-12:** recorte funcional e conteúdo fornecido demonstrados em QA, preparado para disponibilização pelo fluxo editorial. **Restam instalação do conjunto, preparação/revisão/publicação autenticada das11fichas no destino e confirmação da consulta real pelo investidor**; visual continua pendente com conector indisponível. Não declarar ACT-12 encerrado ou o portal integral. Dados de sede/balcões, condições ou cobertura não documentados conservam o estado por confirmar; não bloqueiam os restantes dados fundamentados. Resíduos L1–L7/restanteL8/L9–L16 e outros módulos mantêm o saldo anterior.
### Utilização do directório financeiro
No dossier, escolha **Instituições financeiras e apoios**; o contexto da oportunidade é conservado. No catálogo /fundos seleccione **Local de interesse** e **Necessidade de financiamento**, pesquise e confira as mensagens de cobertura/condições por confirmar. Na ficha, leia separadamente presença física, cobertura do produto, fonte/data, contacto e disponibilidade. Escolha o produto e confirme apenas a conservação da referência privada; **Guardar e continuar no dossier** não envia candidatura. A secção **Referências de financiamento do dossier** conserva a fotografia consultada e permite abrir a ficha actual, que poderá ter mudado ou sido retirada. Resposta incerta permite repetir a mesma intenção sem duplicação; o dossier permite recuperar a escolha.
Para a equipa editorial: **Gerir fundos e programas → Preparar referências fornecidas** apresenta as referências documentais (incluindo FDEL anterior). Escolha cada ficha, confira a fonte, prepare o rascunho pelo botão existente e complete apenas dados fundamentados. Repetir a preparação abre a ficha já existente, sem substituir edições. Use **Alterar** nas secções de contactos/locais/produtos; guarde, confira a prévia da versão e publique com confirmação/motivo no fluxo existente. Não publicar linhas históricas como activas nem marcar oferta desconhecida como aberta. Não é necessário novo perfil, módulo ou cadastro de organizações duplicado.
## 10/11/2026 — APIEX, SWOT e acesso destacado a Concursos instalados
**Homologação instalada e conferida: `1c75a30a601ef00fc187ddf5a64183446d329e6f`**, https://teste-investidor.tempo.co.mz. Inclui APIEX/SWOT37854d02 e correcção do menu autorizada pela Tempo. **Concursos e anúncios** tem agora grupo próprio no menu horizontal comum, com consulta pública e acompanhamentos para utilizadores autenticados; endereço https://teste-investidor.tempo.co.mz/concursos. O módulo já estava activo e apenas a entrada estava dentro de Descoberta; não foi necessário activá-lo novamente. Um rascunho privado e zero versões publicadas preservados.
Conferência final:21hashes Git iguais à release, revisão HTTPS correcta e root/up/login/concursos HTTP200, configuração intacta, zero migrations/importações. Backup fresco restaurado com190tabelas/contagens/checksums iguais e369ficheiros/configuração conferidos;7476dc34 e pacotes anteriores preservados para reversão de código. Recibos `outputs/apiex-swot-concursos-delivery-20261110/{backup,restore,files-restore,verification,delivery}.json`; verificação UTC no recibo. Instalação executada pelo mesmo executor Git, sem alterações de permissões, dados reais, publicação de análises ou submissão externa.
**Saldo:** APIEX e SWOT abaixo deixam de ser pendência de instalação. Acesso pelas ferramentas **Preparação APIEX e SWOT** do dossier existente, conservando a oportunidade seleccionada. Conferência visual no uso normal continua pendente no mesmo registo; não é inferida das provas automáticas. Âmbitos normativos não mapeados continuam indeterminados; os restantes módulos e resíduos L1–L7/restanteL8/L9–L16 conservam o saldo anterior. O portal não é declarado integralmente concluído.
## 10/11/2026 — correcção dirigida das âncoras antes da promoção
O executor recusou c7901592 por um caso frontendS08 (1falha/1788), mantendo7476dc34 activo. Diagnóstico delimitado: a página tinha dois efeitos de âncora e o teste existente conservava uma única sequência de dependências. A abertura SWOT foi integrada no listener de hash já existente, também para mudanças posteriores de #tarefas, sem alterar a guarda/foco/expansão de S08. O teste S08 aceite conserva as suas asserções; acrescentado caso que alterna #tarefas/#S08, verifica isolamento no dossier e limpeza do listener.15casos dirigidos APIEX/SWOT/âncoras aprovados. Nenhuma alteração de dados, permissões, fontes normativas ou configuração; entrega autorizada retomada no mesmo mecanismo, sem mascarar a falha nem desactivar verificações. Recibos finais continuam a determinar a revisão instalada.
## 10/11/2026 — Concursos visíveis no menu principal; instalação agrupada autorizada
Conferência delimitada do destino7476dc34 por SSH autenticado: `procurement.enabled=true`, módulo disponível, tabelas presentes; `/concursos` e raiz HTTP200 com `procurementEnabled=true`. Preservados um concurso em rascunho e zero versões publicadas. O módulo não desapareceu nem estava desligado: a ligação estava dentro de Descoberta. Pedido Tempo «onde foram parar concurso? depois de resolver isso autorizo a instalação»: entrada promovida a grupo próprio **Concursos e anúncios** no menu horizontal comum, com **Consultar concursos e anúncios** e, após autenticação, **Os concursos que acompanho**. Retirada só a ligação duplicada de Descoberta; cinco atalhos principais e autorizações editoriais preservados. Não cria/publica concursos nem muda flags, dados ou schema.
Prova dirigida da navegação:20casos aprovados, incluindo visitante/utilizador, disponibilidade, destinos únicos e selecção correcta de catálogo/ficha/acompanhamentos. Tipos/lint/compilação QA aprovados. Observação anterior `outputs/investment-preparation-20261110/concursos-before.json`; pacote agrupado `outputs/entrega-apiex-swot-concursos-20261110.zip`, preservando o pacote APIEX/SWOT anterior. O conjunto APIEX/SWOT abaixo conserva as provas aceites, sem repetição. Instalação autorizada expressamente após esta correcção; execução e recibo final separados desta prova QA. Visual continua pendente por indisponibilidade do conector.
## 10/11/2026 — APIEX e SWOT no dossier existente: QA concluído, instalação pendente
**Instalado:** `7476dc343098f15a7982e9d7581bf4f59d43bc1a` em https://teste-investidor.tempo.co.mz, promovido às14:28:12UTC e conferido às14:29:07UTC (16:29 locais). Transporte CBF15 deixa de ser pendência de instalação. Delta14hashes Git/HTTPS, root/up/login/revision200, configuração intacta, zero migrations/importações; backup/restauro190tabelas e369ficheiros conferidos. Recibos `outputs/cbf15-carry-delivery-20261110/`; release anterior e pacotes preservados.
**Implementado em QA, ainda não instalado — APIEX:** dossier existente → requisitos fundamentados disponíveis/em falta/não aplicáveis/indeterminados → documentos existentes e complementos → revisão → prévia/ZIP com índice e anexos íntegros → registo da entrega externa, esclarecimento, resposta e decisão com data/referência/comprovativo. Dados do investidor/empresa/contexto/localização/oportunidade reutilizados. Estados em preparação/pronto para entrega/entrega registada/decisão recebida distintos; exportar não submete nem aprova. Snapshot preparado, versões, SHA e pacote privado cifrado preservados; recuperação devolve os mesmos bytes após alteração do contexto, revalidando acesso e segurança dos documentos. Ausência de recepção electrónica APIEX, segundo a orientação Tempo, não bloqueia este percurso; nenhum conector fictício ou formulário oficial inventado. Reutiliza o avaliador e modelos/fontes aceites; períodos/regimes fora do âmbito actualmente mapeado ficam indeterminados, sem conclusão normativa presumida.
**Implementado em QA, ainda não instalado — SWOT:** no mesmo dossier/oportunidade, objecto explícito investimento/território/sector/cluster, evidências do contexto/análise proactiva e Competitividade pública aprovada, quatro dimensões internas/externas, fonte/data/classificação de cada conclusão (facto documentado/hipótese/por confirmar). Sem pontuação, IA ou inferência de ausência pela falta de registos. Gravação/recuperação/histórico paginado e revisão pela equipa com permissões existentes. Alteração/retirada de fontes assinala revisão, conserva fotografia consultada e edições deliberadas. Acções ideia/oportunidade/parceiros/risco continuam no mesmo dossier; risco liga à tarefa nativa com origem própria e repetição sem duplicação. Privado por defeito; publicação exige o fluxo editorial existente, nunca automática. Contextos retirados ficam no histórico privado de leitura, sem reactivação.
Provas sintéticas no QA55441 existente, com rollback: seis casos API/211asserções;16casos frontend dirigidos (incluindo transporte e cabeçalho comum147páginas), tipos, análise estática e lint aprovados. `outputs/investment-preparation-20261110/verification.json`, ZIP/índice/anexos, recuperação SWOT e provas de isolamento/histórico/segurança. Pacote Git agrupado `outputs/entrega-apiex-swot-20261110.zip`, base exacta7476dc34, reversão de código sem apagar auditoria, pacotes privados ou tarefas posteriores. Nenhuma migration/base/porta/conta real nova, submissão real ou publicação. Compilação QA separada; provas anteriores reutilizadas.
**Saldo concreto:** APIEX e SWOT acima encerrados em implementação QA; instalação deste conjunto e conferência visual continuam pendentes. O conector mantém “The browser access host is unavailable”; SSR/testes não substituem visual, sem novo ciclo de reinícios. Os restantes módulos conservam o saldo anterior, incluindo resíduos L1–L7, restanteL8 e L9–L16. Isto não declara o portal integralmente concluído.
### Utilização — preparação APIEX e SWOT
Abra o dossier autorizado e escolha **Preparação APIEX e SWOT** nas ferramentas/documentos. Se houver várias oportunidades, escolha primeiro o contexto; o título e histórico identificam sempre essa escolha. Em APIEX, confira o resumo reutilizado e use **Alterar** para corrigir no editor existente. Confira os requisitos e os documentos disponíveis, complete os que faltam na biblioteca existente, guarde e reveja. A prévia apresenta o índice; exporte e conserve o ZIP. Registe depois a entrega efectivamente realizada externamente, com data, referência e comprovativo; esclarecimentos/respostas/decisão são eventos separados ligados à exportação. Uma decisão recebida não implica aprovação.
Na aba SWOT, escolha objecto/âmbito, consulte as evidências e preencha as quatro dimensões com fonte/data/classificação. Guarde, peça/recolha a revisão com as permissões actuais e associe uma acção à conclusão. A ligação de risco abre a tarefa no mesmo dossier, sem substituir tarefas anteriores. Histórico permite recuperar versões; restauro de SWOT cria novo rascunho sem eliminar entregas externas ou acções. Se uma gravação tiver resposta incerta, repita a mesma intenção ou consulte e compare a versão guardada; a substituição das edições locais é explícita. Fontes alteradas exigem nova revisão, sem reescrever conclusões. Análises ficam privadas; eventual divulgação passa pelo fluxo editorial existente.
## 10/11/2026 — transporte documental CBF15 em QA
Homologação mantém `d216830b`; este delta ainda não está instalado. Tipos/lint/Pint/buildQA aprovados; pacote com aplicação/reversão exactas,sem migrations/dados/credenciais. Pendência encerrada no recorte normal residente2026/ano civil/MZN: crédito de investimento de origem anterior do mesmo contribuinte/projecto/local/regime, com activo/documento/parcela, exercício de origem, NUIT/projecto de origem, taxa original e qualificação históricas documentados. Exercício de origem anterior a2026,dentro da janela original e não anterior à aquisição; no projecto em funcionamento,a aquisição não precede o início aplicável. Não renova o prazo nem recalcula origem a outra taxa. Valor corrente/ausência e uso efectivo agregado exigem reconciliação explícita; histórico de simulações não equivale a utilização fiscal.
Cálculo → POST/PUT do mesmo rascunho privado → GET → recálculo → HTML/dossier demonstrados no QA55441 existente com rollback. Exemplo sintético recuperado: nominal de origem4.000,uso efectivo anterior1.000,disponível3.000,quota documental do projecto200 → dedução200,não utilizado2.800,saldo500MZN. Início2022 → quinto exercício2026; mudando o início para2021 → sexto,parcela expirada3.000,disponível/dedução0,saldo700,com activo,uso anterior e origens preservados. Seis/sete resultados históricos conservados,incluindo o resultado ordinário aceite. Provas `outputs/irpc-cbf15-carry-20261110/{carry,expired}-verification.json` e relatórios respectivos; API um caso/28asserções novas,30casos frontend nas fronteiras alteradas incluindo formulário real compilado e prevenção de duplicação. SSR/formulário simulado não é navegador/visual. FonteCBF15(4/7),APIEX2025 PDF71–72 já aceite,reutilizada.
A preparação mantém os activos correntes e exige explicitar os dados em falta; activos de origem e correntes partilham a capacidade documental e a detecção de duplicação entre mapas. Origem com outro NUIT/projecto/taxa,exercício incoerente,uso superior ao nominal ou declaração de ausência incompatível não produz dedução. Sem pagamento,reembolso,consumo oficial ou criação automática de saldo transportado. Livros antigos permanecem nos formatos1/2; só o transporte preparado usa livro3/detalhe2,para o código anterior recusar em vez de ignorar o crédito novo. Snapshots e históricos aceites não são reescritos; reversão é de código e não deve sobrescrever dados posteriores.
Saldo preservado: outros projectos/localizações/regimes/cumulações/EP/L7,combinação positiva com transportes económicos/internacionais/PEC anteriores,repartição automática,benefícios/formulários oficiais e restantes resíduos L1–L7/restanteL8/L9–L16 mantêm os seus recortes. Os módulos não fiscais mantêm o saldo existente,sem nova auditoria geral. Próximo trabalho: primeira combinação residual executável respeitando a ordem de dedução e quota do projecto,ou percurso independente do saldo existente; visual continua pendente no mesmo registo. Não é conclusão integral do portal.
## 10/11/2026 — menus horizontais instalados
**Homologação: `d216830b068af8a7a40d43993795639e73aaed78`**, promovida às13:56:27UTC e conferida às13:56:54UTC (15:56 locais), em https://teste-investidor.tempo.co.mz. Delta10hashes Git e revisão HTTPS iguais; root/up/login/revision HTTP200, configuração preservada, zero migrations/importações. Backup/restauro190tabelas com contagens/checksums iguais e369ficheiros/configuração conferidos; revisão d5cb41b1 preservada para reversão. Recibos `outputs/horizontal-menu-delivery-20261110/{backup,restore,files-restore,verification,delivery}.json`. Pacote anterior e dados/históricos preservados; sessãoSSH pronta para continuação autenticada, sem credencial em disco.
O recorte dos menus horizontais abaixo deixa de ser pendência de instalação: cabeçalho comum em páginas públicas/autenticação/definições/privadas, sem sidebar global concorrente; Descoberta sem repetição dos cinco atalhos mantidos na entrada principal. Prova estrutural dos146layouts e23casos dirigidos reutilizada; não é conferência visual. O conector continua indisponível,sem novos reinícios ou alterações ao navegador. Visual mantém-se pendente no mesmo registo. O restante saldo de funcionalidades por módulo e L1–L7/restanteL8/L9–L16 não é encerrado por esta instalação.
## 10/11/2026 — janela CBF15 instalada; menus horizontais uniformizados em QA
**Homologação: `d5cb41b111cac95c431a3072b8c4cc0141481ec2`**, promovida às13:43:40UTC e conferida às13:44:18UTC (15:44 locais). Delta12hashes Git e revisão HTTPS iguais; root/up/login/revision HTTP200, configuração preservada, zero migrations/importações. Backup/restauro190tabelas e369ficheiros/configuração conferidos,89e6d72c preservada. Recibos `outputs/cbf15-window-delivery-20261110/`. Janela de cinco exercícios CBF15 deixa de ser pendência de instalação; transporte de créditos anteriores e restantes resíduos fiscais continuam separados.
**Delta posterior em QA, ainda não instalado — pedido expresso da Tempo10/11, corrigido para menus horizontais com dropdown:** cabeçalho comum passa a ser montado uma vez pelo layout global em todas as páginas; área privada usa o mesmo cabeçalho sem navegação lateral concorrente. Autenticação, definições, gestão e páginas públicas conservam conteúdo e breadcrumbs; filtros de capacidades, direitos e destinos privados existentes preservados. Esconder cabeçalho na impressão mantém-se. Os cinco atalhos (Porquê investir,Partir de uma ideia,Consultar oportunidades,Simulador Fiscal,Sectores) ficam intactos na entrada principal. Descoberta conserva localizações/mapa,catálogos e módulos opcionais,sem repetir os atalhos; Partir de uma ideia já estava fora desse grupo. CSS responsivo e implementação comum dos dropdowns/teclado/Escape/toque reutilizados,sem novo menu alternativo.
Conferência estrutural dos layouts das146páginasTSX existentes: exactamente um cabeçalho comum por composição, incluindo páginas privadas,autenticação/definições, sem sidebar global; conteúdo/contexto/breadcrumbs e cinco atalhos preservados. Provas `tests/frontend/portal-horizontal-shell.test.mjs`, navegação e renderização dirigida (23casos aprovados). É SSR com dependências de UI substituídas,não prova visual de navegador. Tentativa única de inventárioCUA nesta continuação devolveu exactamente `Browsers: Error: The browser access host is unavailable`; nenhuma alteração/reinício de navegador. Conferência visual mantém-se pendente no registo existente. Tipos/lint/build QA aprovados, logs desta entrega; base activa/dados/permissões intocados.
O saldo integral dos restantes módulos e L1–L7/restanteL8/L9–L16 permanece no plano existente; nenhuma contagem de páginas/testes declara o portal concluído.
## 10/11/2026 — janela de cinco exercícios CBF15 em QA
Homologação mantém `89e6d72c`; o delta seguinte ainda não está instalado. Pendência encerrada em QA: contagem inclusiva dos cinco exercícios civis do CBF15(4/7) na liquidação2026, para investimento efectivo do próprio2026. Início de exploração para projecto novo; início dos procedimentos após aprovação para projecto em funcionamento. Não usa cinco anos móveis nem reinicia o prazo pela data de aquisição. Início2022, mesmo em31/12 → quinto exercício2026, dedução200 pela quota residual, saldo500MZN. Início2021 → sexto exercício2026, prazo expirado, dedução0 e saldo700; activo e documento preservados. Datas inválidas/futuras e activos antigos apresentados como investimento corrente permanecem indeterminados/recusados, não zero por omissão.
Cálculo → POST/PUT do mesmo rascunho privado → GET → recálculo → HTML/dossier demonstrados no QA55441 existente, com rollback, cinco/seis resultados históricos e todos os anteriores preservados. Provas `outputs/irpc-cbf15-window-20261110/{fifth,expired}-verification.json` e relatórios respectivos. Modelo `IRPC-CBF15/2`, livro existente versão2 sem migration; snapshots aceites não são reescritos. Fonte já aceite APIEX2025 PDF71–72,CBF15(4/7), reutilizada sem nova validação de autenticidade. Provas anteriores reutilizadas; apenas fronteiras alteradas conferidas.
Saldo: transporte de créditos de investimento de anos anteriores, repartição automática/múltiplos projectos, EP/L7/cumulações/outros benefícios/formulários oficiais continuam por implementar nos respectivos recortes. A janela corrente não declara o transporte entregue. Resíduos L1–L7, restanteL8 e L9–L16 e saldo dos módulos não fiscais preservados. Conferência visual pendente no mesmo registo; portal não integral. Próximo trabalho executável: transporte documental CBF15 dentro da mesma janela, com consumo anterior reconciliado e sem duplicar o investimento corrente.
## 10/11/2026 — CBF15 instalado na homologação
**Revisão instalada: `89e6d72c1e86df8eb86e7be74583659975585036`**, promovida às13:27:59UTC e conferida às13:28:21UTC (15:28 locais), em https://teste-investidor.tempo.co.mz. Os17hashes do delta coincidem com Git; revisão/root/up/login HTTP200, configuração preservada, zero migrations/importações. Backup fresco/restauro190tabelas com contagens/checksums iguais e369ficheiros/configuração extraídos e conferidos;7c8d4726 preservada para reversão. Recibos `outputs/cbf15-delivery-20261110/{backup,restore,files-restore,verification,delivery}.json`; pacote imutável `outputs/entrega-cbf15-20261110.zip`, SHA25613bdecef2697edcc821d6fa490586d0c2f4b171b4308c655aa60ba5632664c83.
O recorte CBF15 abaixo deixa de ser pendência de instalação: primeiro exercício2026, residente geral/MZN, activos e condições documentados, colecta integral do projecto ou quota residual documentada, cálculo/gravação/recuperação/relatórios com histórico. As notas QA anteriores ficam como histórico das provas aceites. Outros exercícios/transporte/caducidade, repartição automática/múltiplos projectos, EP/L7/cumulações/outros benefícios/formulários e restantes resíduos L1–L7/L8/L9–L16 permanecem no plano existente. Próximo trabalho: residual executável de CBF15/L8, preservando o recorte instalado e sem repetir as provas aceites. Conferência visual continua pendente no mesmo registo; portal não declarado integral.
# Rastreabilidade, utilização e formação
## 10/11/2026 — acordo PT–MZ instalado; crédito CBF15 demonstrado em QA
**Homologação: `7c8d4726e9f93325480e2ee6e5e790e9adbcaf19`**, promovida às12:36:07UTC e conferida às12:37:11UTC (14:37 locais). Revisão HTTPS/10hashes iguais, quatro entradasHTTP200; configuração preservada, zero migrations/importações. Backup/restauro190tabelas e369ficheiros/configuração conferidos, b10b35f3 preservada. Recibos `outputs/cdt-agreement-delivery-20261110/`. Falha inicial de recepção Git delimitada ao directório objects/7c pertencente aroot: apenas proprietário/grupo ajustados a conta do serviço,modo0755 inalterado; pedido original validado recolocado sem alterar corpo/SHA ou controlos. Recibos git-permission-repair/request-retry; não foi falha do portal nem alteração de segurança do navegador. Sessão SSH preservada para continuação autenticada, sem credencial em disco.
**Pendência efectivamente encerrada em QA, ainda sem instalação:** CBF15, crédito fiscal por investimento efectivo no primeiro exercício2026, residente geral/ano civil/MZN, colecta inteiramente da actividade do projecto ou quota residual documentada após créditos anteriores e sem outros benefícios próprios/cumulações ou transportes positivos combinados. Reutiliza qualificação CBF de L7 e fonte APIEX já aceite (recibo irpc-l7-common-qa-20261026,compilação oficial2025,PDF66–72,art15PDF71–72), não importação como activação. Localização do investimento,aprovação/condiçõesCBF3/9/10/regime genérico13,início aplicável e activos/documentos exigem correspondência própria. Activo novo corpóreo,nacional,produtivo e fora15(6); condições negativas/exclusões documentadas produzem crédito zero nessa linha,preservando os dados. Informação em falta não é zero.
Percurso completo: preparar detalhe → calcular após económico/internacional e antesPEC/RF/PPC → gravar/actualizar pelo writer privado existente → recuperar → recalcular → HTML/dossier com fornecedor/documento/parcela/valores/qualificação/período/histórico. Maputo cidade5%: investimento10.000 → crédito500 → saldo200 MZN. Restantes províncias10%: crédito1.000 → saldo−300, recuperável simulado devido aoPPC. Cenário de limite: nominal4.000,dedução1.400,crédito não usado2.600,PEC não utilizado200,PPC500; nenhum reembolso de crédito/PEC ou saldo oficial criado/consumido. Provas `outputs/irpc-cbf15-20261110/{city,province,capped,partial}-verification.json` e relatórios respectivos. Quatro versões do mesmo rascunho API em transacção revertida QA55441,com histórico anterior e versões seguintes preservados; zero alterações activas.
Quota residual documentada: nominal4.000, limite do projecto200, dedução200, não utilizado3.800 e saldo500 MZN; recuperada pela API com quatro resultados históricos. Quota superior à colecta global remanescente, negativa ou sem fundamento é recusada. Não há repartição automática de rendimentos/custos/perdas. Tipos, lint/Pint e compilação QA aprovados; provas anteriores reutilizadas.
Livros anteriores L8 permanecem versão1,com chaves/resultados inalterados quando sem CBF15. Só o livro com este novo detalhe usa versão2: cliente anterior recusa o formato,conservando o snapshot,em vez de recalcular ignorando o novo benefício. Não é migration de base. O resumo antigo dos benefícios fica conservado e não reaplicado; ausência dos restantes benefícios exige declaração/reconciliação explícita,mesmo quando CBF15 é ausente. Fonte documental tem capacidade por factura/parcela e entre mapas da entidade; mudar id/data não duplica investimento. Repetir simulação não é uso fiscal anterior.
**Saldo preservado:** CBF15 de outros exercícios/transporte/caducidade,repartição automática/múltiplos projectos,EP/L7/cumulações,outros benefícios e obrigações/formulários oficiais continuam separados. Ausência/zeroCBF15 não bloqueia os saldos anteriores aceites. Restantes L1–L7/L8/L9–L16 e módulos não fiscais mantêm o saldo existente; o portal não está integral. Visual permanece pendente. Conjunto CBF15 preparado para entrega sobre7c8d4726,sem instalação nesta entrada.
## 10/11/2026 — b10b35f3 instalado; acordo institucional PT–MZ em QA
**Homologação: `b10b35f30830922dae00afde50ac2bdb0e8cef6f`**, promovida às12:15:13UTC e conferida às12:16:31UTC (14:16 locais). Revisão HTTPS e16hashes Git iguais, quatro entradasHTTP200; configuração preservada, zero migrations/importações. Backup/restauro190tabelas com contagens/checksums iguais e369ficheiros/configuração conferidos;60b52fc8 disponível para reversão. Recibos `outputs/government-fraction-delivery-20261110/{backup,restore,files-restore,verification}.json`. Sessão SSH mantida aberta para continuação autenticada, sem credencial em disco. Isenção por devedor público11(3)a e fracções económicas L6 deixam de ser pendências de instalação; as anotações anteriores QA são históricas nesses recortes.
**Delta posterior QA, ainda não instalado:** correspondência11(3)b para instituição/organismo beneficiário corrente que concedeu o financiamento no âmbito de acordo concluído entre os governos PT–MZ. Beneficiário, financiamento ligado aos juros e acordo/cláusula aplicável exigem suporte próprio. País, nome da entidade ou qualificação institucional isolada não activam isenção. Fundamento distinto do devedor público11(3)a; limpar/trocar a escolha invalida a decisão corrente, sem apagar resultado histórico nem reutilizar campos ocultos como nova decisão.
Cálculo → gravação/GET pela API existente no QA55441 com rollback → recuperação → HTML/dossier demonstrados. Exemplo sintético: juros brutos60, imposto pago10 e fracção nacional8, com acordo qualificado → limite convencional0/crédito0/saldo708 MZN; sem reembolso, transporte ou consumo oficial e sem alterar L6. Relatório conserva instituição, financiamento e acordo. Prova `outputs/irpc-pt-mz-agreement-20261110/verification.json`; tipos/lint/Pint/build QA aprovados e provas anteriores reutilizadas.
Pacote `outputs/entrega-cdt-acordo-20261110.zip`, base exactab10b35f3, aplicação/reversão conferidas; entrega seguinte expressamente autorizada durante esta continuação, ainda sem instalação nesta entrada. Restantes beneficiários/regimes11(3)b, EP, relações especiais e demais CDT/benefícios/obrigações conservam correspondência própria; saldo L1–L7/restanteL8/L9–L16 preservado. Conferência visual continua pendente no mesmo registo; portal não integral.
## 10/11/2026 — CDT instalada; isenção governamental e precisão económica em QA
**Homologação: `60b52fc8c7446cbbee904a4411eca8ad284aa7ad`**, promovida às11:49:26UTC e conferida às11:50:11UTC (13:50 locais). Delta13hashes Git e revisão HTTPS iguais; portal/login/up/revision HTTP200, .env preservado, zero migrations/importações. Backup fresco/restauro190tabelas com contagens/checksums iguais e369ficheiros/configuração extraídos/conferidos; ac67f9c1 disponível para reversão. Recibos `outputs/cdt-pt-delivery-20261110/{backup,restore,files-restore,verification}.json`. Sessão SSH encerrada, credencial não guardada. Royalties/dividendos/juros ordinários PT–MZ deste recorte deixam de ser pendência de instalação; não reabrir provas aceites.
**Novos fechos dirigidos apenas em QA, ainda não instalados:**
- CDT11(3)a: devedor Estado português, subdivisão política/administrativa ou autarquia, expressamente qualificado e documentado; mantém residência, fonte, definição, período e restantes condições do recorte. Limite convencional0, imposto pago10 sem crédito elegível e saldo708 MZN no exemplo. Não gera reembolso/transporte nem altera L6. Beneficiários/acordos11(3)b e outras situações permanecem separados. Prova `outputs/irpc-pt-mz-government-20261110/verification.json`, com API/gravação/recuperação/HTML/dossier e fundamento do devedor no relatório.
- L6: diferença económica conserva fracções exactas, incluindo subcêntimos e n/d, pelo mecanismo racional existente. Remove a limitação técnica de exigir cêntimos inteiros neste mapa; não cria regra de arredondamento fiscal. Montante do tópico é reconciliado por igualdade racional, evitando tratar0,006 como0 ou aceitar truncamento. Exemplo de IRPC subjacente20,01: nominal12,006, efeito112,006, base4512,006, colecta1443,84192; reconferida L8, saldo723,83592. Repetição/substituição não acumula; valores já considerados/históricos preservados. Prova `outputs/irpc-economic-fraction-20261110/verification.json`, até API/recuperação/HTML/dossier/L8. Outros mapas e arredondamento de declarações oficiais conservam âmbito próprio.
Conjunto novo agrupável sobre60b52fc8; não integra a revisão instalada. Conferência visual permanece pendente no mesmo registo S08. Resíduos L1–L7, restante L8 e L9–L16 preservados; nenhum lote ou portal declarado integral. Próximos ramos: CDT11(3)b e restantes correspondências concretas, obrigações/benefícios e demais percursos conforme saldo existente.
Conjunto posterior preparado em `outputs/entrega-isencao-precisao-20261110.zip`, base exacta60b52fc8. Tipos/lint/Pint/build QA aprovados, aplicação/reversão conferidas em índices temporários. Não instalado; provas e pacotes anteriores preservados.
## 10/11/2026 — económico instalado; CDT Portugal avançada em QA
**Homologação: `ac67f9c13a6561b66c5da788a48849beac90a905`**, promovida pelo executor Git existente às11:32:14UTC e conferida às11:33:16UTC (13:33 locais). Revisão HTTPS e27hashes Git iguais; portal/login/up/revision HTTP200; .env preservado, zero migrations/importações. Backup/restauro190tabelas com contagens/checksums iguais e369ficheiros/configuração extraídos/conferidos. Release c359364d conservada. Recibos `outputs/economic-special-delivery-20261110/{backup,restore,files-restore,verification}.json`. Credencial não guardada; sessão SSH encerrada. Inclui quota económica, inclusão/reconciliação ordinária e especial L6, partilha64(3) e associações64(4). Entradas anteriores «apenas QA» são históricas nestes recortes; não reinstalar por consequência.
**Delta posterior, ainda QA:** correspondência CDT PT–MZ para royalties ordinárias não digitais, dividendos e juros ordinários de fonte portuguesa de residente convencional em Moçambique. País não activa a convenção. Exige beneficiário efectivo, definição/natureza, fonte/pagador, ausência de EP/instalação fixa ligada, período e suporte documentados; relações especiais verificadas nos juros/royalties, isenção governamental11(3) separada. Limite10% do bruto nos ramos suportados; dividendos usam alteração do Protocolo2009 art6, evitando taxa15% do texto1992. Não usa retenção L5 em sentido inverso nem tax sparing presumido. Mantém limite manual/históricos e parcela líquida nacional.
Prova sintética por ramo: bruto60 → limiteCDT6, imposto pago10/fracção nacional8 → crédito6 → saldo702 MZN. Cálculo/gravação/recuperação/HTML/dossier por API existente no QA55441; sem novo writer, schema, direito ou escrita activa. Provas `outputs/irpc-pt-mz-{credit,dividend,interest}-20261110/verification.json`. Fracções mantêm exactidão, sem arredondamento fiscal presumido. País/condição/período inválidos deixam apenas o caso dependente indeterminado. Este delta não integra ac67f9c1.
Restam CDT de outros Estados/naturezas, EP/instalação fixa, juros governamentais, excessos/relações especiais e casos fora do recorte, além dos demais resíduos L1–L7/L8/L9–L16. Conferência visual continua pendente no mesmo registo. Não declarar L8 ou o portal integralmente concluídos.
Conjunto CDT agrupado em `outputs/entrega-cdt-pt-20261110.zip`, base exacta ac67f9c1, aplicação/reversão conferidas; tipos/lint/Pint e build QA aprovados. Instalação deste delta ainda não executada.
## 10/11/2026 — crédito económico: partilha e associações em QA
Homologação mantém `c359364d`. Avanço posterior em QA, sem instalação: art64(3) distingue a parcela elegível do rendimento da partilha e o excesso com natureza de mais-valia; art64(4) distingue associação em participação e à quota, exigindo tributação efectiva documentada na participação. Reutiliza cálculo60%, prova do imposto subjacente, limites, prioridades, isolamento e writer privado existentes. Não calcula a mais-valia como crédito nem pressupõe inclusão L6 ou reconferência da colecta. Campos antigos e históricos são preservados; fontes incompletas ficam recuperáveis e indeterminadas.
Critério demonstrado: calcular → gravar/GET pela API existente no QA55441 com rollback → recuperar exactamente → HTML e relatório do dossier. Partilha sintética: valor atribuído200, entradas100 e rendimento documentado150 → elegível100/excesso50 → quota35,84/crédito12/saldo688 MZN. Associação documentada100/imposto20 → crédito12/saldo688 MZN, sem alterar L6. Provas `outputs/irpc-economic-liquidation-20261110/verification.json` e `outputs/irpc-economic-association-20261110/verification.json`. Não são actos fiscais nem consumo de saldos oficiais.
Conjunto de fontes compatíveis com quota e inclusão ordinária agrupado em `outputs/entrega-economica-especial-20261110.zip`, base c359364d, aplicação/reversão conferidas em índices temporários. Pacotes anteriores intactos. Inclusão/correcção especial em L6 também demonstrada em QA: reconcilia rendimento total da partilha (incluindo mais-valia) uma só vez, enquanto a quota usa apenas a componente elegível; associações reutilizam a qualificação própria. Prova outputs/irpc-economic-special-inclusion-20261110/verification.json: efeito162, base4562/colecta1459,84, quota35,84/saldo739,84 recuperados na API e relatórios. Casos transitórios, fracções de cêntimo, CDT, benefícios/obrigações e restantes resíduos L1–L7/L9–L16 continuam no saldo. Conferência visual pendente. Próximo trabalho funcional: correspondência CDT automática nos casos suportados pelas fontes existentes; eventual instalação deste conjunto é acção separada. Tipos/lint/Pint/build QA conferidos no conjunto final.
## 10/11/2026 — inclusão/correcção económica L6 em QA
Instalado: `c359364d`. Quota económica e inclusão/correcção L6 ordinária demonstradas em QA, sem instalação. Pacote anterior `outputs/entrega-economica-l6-l8-20261110.zip` preservado; novo delta posterior agrupado separadamente. Conferência visual pendente no registo existente.
Escolha explícita da origem L8, reconciliação de lucros/crédito já considerados, prévia da diferença e confirmação precedem o cálculo/gravação existentes. Repetição substitui a parcela; não acumula. Prova: +112 MZN leva lucro tributável a6812, base4512 e colecta1443,84. Montantes já incluídos100/12 dão efeito0; excesso150/20 corrige−58. Origem L8 anterior fica desactualizada; reconferida a nova colecta, quota35,84 e saldo723,84 mantêm-se na API/recuperação/HTML/dossier, com históricos preservados. Provas `outputs/irpc-economic-inclusion-20261110/verification.json` e `outputs/irpc-economic-quota-20261110/verification.json`. Tipos/lint/Pint/build QA aprovados.
Âmbito ordinário residente geral2026/MZN. Fracções de cêntimo exigem tratamento próprio, sem arredondamento presumido. Casos especiais, CDT e obrigações continuam no saldo. Resíduos L1–L7 e L9–L16 preservados; L6/L8 não integrais. Registos anteriores abaixo são históricos.
## 10/11/2026 — conjunto Educação/L8 instalado e residual económico avançado
**Instalado e conferido em c359364de3f65599f2248540a5b040604e8490cc**, por fast-forward normal em main a partir de ff726e57. Árvore058910eb26f566c0486a20c1e7fc12f06bbe06a5 igual ao pacote QA. Executor existente terminou às10:54:50UTC; revisão HTTPS/portal/login/upHTTP200 e14hashes de fontes conferidos às10:55:21UTC. Zero migrations/importações; configuração idêntica; release anterior preservada. Backup fresco em `/var/backups/investmoz-education-l8-20261110/backup`,190tabelas efectivamente restauradas/checksums iguais num contentor sem rede,369ficheiros/configuração restaurados e conferidos em pasta privada. Credencial não guardada; sessão SSH encerrada após conferência. Recibos `outputs/education-l8-delivery-20261110/{backup,restore,files-restore,verification}.json`. Pacotes selados anteriores preservados. Controlos do executor obrigatórios,sem nova ronda manual de provas aceites.
Ficam utilizáveis no destino os dois recortes: revisão educativa conserva preparação/envio incerto ao consultar histórico; crédito internacional oferece derivação ordinária da fracção nacional. Instalação destes recortes deixa de ser pendência;conferência visual continua separada. Outros critérios dos módulos não são encerrados por esta instalação.
**Próximo residual executável avançado após a instalação — QA,sem instalação nova:**quota proporcional do crédito económico ordinário,CIRPC64(1–2)após acréscimo53. Fonte aceite Lei34/2007,PDF18/p768-(72),reutilizada;60% do imposto correspondente e qualificação/exclusão40 já validados pelo mecanismo existente. Novo modo calcula `colecta L6 × (lucros incluídos + crédito nominal já acrescido) / matéria colectável L6`,com versão/origem/referência no resultado e relatório. Não altera a matéria colectável,não duplica o acréscimo,não calcula automaticamente a sua inclusão. Modo documentado antigo por omissão;limite anterior conservado,não utilizado no modo derivado. Fonte/base desactualizada,acréscimo incompleto,correspondência ausente e soma de componentes acima da base impedem o saldo dependente. Casos64(3/4),L7 e outras situações conservam o saldo próprio.
Quatro casos novos frontend,cálculo/recuperação/HTML/dossier;API privada existente,num novo caso/9asserções,DatabaseTransactionsrollback na mesma QAproactive_investment_20261027_test:55441,actor1021existente. Novo exemplo sintético:lucros100+acréscimo60,base4400/colecta1408→quota256/5=51,2MZN;saldo3244/5=648,8MZN.Sem saldo fiscal consumido. Prova `outputs/irpc-economic-quota-20261110/verification.json`. Tipos/lint ePint aprovados;buildQA em log próprio. Nenhuma prova anterior de Educação/fracção nacional reaberta. Estado é QA,ainda não instalado nem visualmente aceite. L8 permanece parcial;resíduosL1–L7 eL9–L16 mantidos. Próximo:inclusão/correcção económica na base com a correspondência própria e restantes ramos já identificados,sem repetir os recortes fechados.
## 10/11/2026 — L8: fracção nacional ordinária demonstrada até ao relatório em QA
Pendência funcional encerrada **em QA no recorte ordinário**: opção explícita de derivar a fracção nacional do crédito internacional da colecta L6 corrente e da parcela líquida estrangeira correspondida à matéria colectável. Fonte aceite reutilizada: CIRPC65(1)(a/b),65(2),Lei34/2007,PDF18/p768-(72),e ordem66/RIRPC21. Não se exige nova validação da fonte oficial. A fórmula técnica `colecta antes de créditos × parcela líquida / matéria colectável` fica limitada à colecta normal corrente, base positiva, inclusão documentada, mesmo titular/exercício/país e afectação líquida identificada. O portal não inventa nem reparte custos/perdas/deduções; valores acima da base ou parcelas por país que em conjunto excedam a base impedem o saldo dependente.
O modo documentado anterior continua por omissão nos livros existentes. Limite/suporte manuais ficam conservados quando se escolhe derivação, mas não são utilizados como limite activo nesse modo. Nova correspondência fica ligada à chave da colecta L6; origem alterada exige nova conferência. Método inválido é recusado. Os resultados antigos congelados não são reescritos. Convenção/aplicabilidade/limite continuam documentados por caso; efeitos L7 e outros regimes/períodos/moedas conservam correspondência própria. Não há consumo de crédito oficial, pagamento ou submissão fiscal.
Percurso demonstrado: cálculo → gravação/actualização/GET pelos endpoints privados existentes → recuperação com snapshot íntegro → recálculo corrente → HTML e relatório financeiro do dossier. Exemplo exclusivamente sintético:base4400/colecta1408/parcela100 produz fracção32;imposto estrangeiro100 limitado32,PEC200/PPC500 deixam676MZN de saldo simulado. Parcela0,01 produz fracção exacta2/625,sem arredondamento fiscal presumido. Correspondência em falta deixa resultado indeterminado. LimiteCDT6 continua a reduzir a dedução e a produzir702 no cenário documentado. Provas em `outputs/irpc-national-fraction-20261110/recovered-reports.json`,HTMLs/relatórios no mesmo directório.
Seis casos frontend novos (cinco iniciais e um adicional de afectação por países),um caso API/23 asserções apenas do delta, com transacção revertida na QA existente `proactive_investment_20261027_test:55441`,actor sintético existente1021. Sem novas bases,concessões,migrations ou alterações aos documentos/históricos anteriores. Tipos e lint dirigidos aprovados; compilação QA final acompanhada no log próprio. Provas aceites de IVA/IRPC/PEC/transporte/económico não repetidas. Reprodução dirigida: `node --test tests/frontend/irpc-national-fraction.test.mjs`; gerador de amostras `tools/irpc-national-fraction-fixtures.mjs`; writer QA `php tools/irpc-national-fraction-qa.php test`; conferência dos relatórios recuperados `node tools/irpc-national-fraction-recovered.mjs`.
**L8 continua parcial; fiscal não se limita a L8.** Transporte ordinário e crédito económico já entregues em569a1cd7 preservados. Resíduos L1–L7 e lotes L9–L16 estão explicitados no saldo por módulo existente, sem presumir fecho integral a partir de uma entrada ou taxa. Novo delta L8 e continuidade de Educação são compatíveis (sem schema/rotas/writers novos), agrupados em `outputs/entrega-educacao-l8-20261110.zip`, com manifesto/fontes/patch/reversão contra ff726e57; ainda não instalados. Tipos/lint/Pint/compilação QA final aprovados. O índice de trabalho e o pacote anterior ficam preservados; conferência em índices temporários verifica aplicação e reversão, sem instalar ficheiros. Homologação mantém ff726e57; conferência visual permanece separada. O próximo trabalho é outra pendência funcional do saldo, não repetir esta prova.
## 10/11/2026 — execução de conclusão integral após instalação ff726e57
Entrega de fontes até `ff726e57` instalada e verificada por HTTPS na homologação; inclui S08 e os restantes ajustes do pacote selado. Disponibilidade de código não é uma nova atribuição real nem prova de todos os percursos privados. Recibo `outputs/reviewed-install-20261110/verification.json`: revisão servida, 44 hashes do delta, configuração preservada, zero migrations/importações e release anterior conservada. As anotações antigas «sem instalação» continuam históricas, superadas neste âmbito. Conferência visual S08 permanece no registo próprio, sem reinstalação ou bloqueio transversal.
Saldo actual por módulo/percurso reconciliado na visão consolidada existente e em 35 IDs da matriz, sem criar novas linhas ou apagar estados/critérios anteriores. Os 3.555 registos incluem cláusulas/campos sobrepostos, não 3.555 funcionalidades independentes. Restantes células preservadas; recibos aceites reutilizados, sem nova auditoria geral.
**Utilização avançada nesta passagem — Educação:** no detalhe privado de uma declaração, preparar o fundamento da revisão e consultar Anterior/Seguinte no histórico deixa de exigir abandonar a revisão. As ligações conservam estado/posição; resposta nova actualiza a declaração mas não adopta silenciosamente a versão analisada. Um envio sem confirmação continua recuperável com a chave original. Alterar declaração/tipo/actor mantém a chave de isolamento do componente. Sair para outra página continua protegido pela guarda existente. Não houve novo endpoint, decisão automática, writer ou efeito na base.
Quatro verificações novas de comportamento em `tests/frontend/education-review.test.mjs`, seleccionadas por `history paging`, aprovadas. Tipos globais, lint dirigido e compilação QA concluídos com código 0. O build QA existente foi actualizado, sem novo ambiente ou dados. Logs `storage/logs/education-history-continuity-20261110-{types,build}.log`. Sem repetir testes aceites ou suites PHP. O delta é local e ainda não instalado remotamente; não há prova de navegador nem persistência durável do motivo entre sessões. Saldo no módulo: essa retoma durável, jornadas externas com representação/vínculos documentados e conferência visual. O proprietário já indicou que não existe cadastro/documento aprovado desses vínculos; isso bloqueia apenas os ramos externos dependentes.
Próximo trabalho funcional na ordem preservada: saldo fiscal L8 com fontes aceites, seguido de modalidades de Imobiliário/Bens de consumo/franquias e Estatística/IA, sem reabrir os fechos aceites. Não há exclusão de funcionalidades previstas nem conclusão global presumida.
## 10/11/2026 — preparação conjunta de contador e aliases
[Entrega conjunta](../v2/auditoria-portal/entrega-conjunta-contador-aliases-2026-11-10.md) preparada contra `main`/`origin/main` em `2209b838`, com patch único, reversão e manifesto. Oito casos novos/56 asserções conferem contexto sem base de dados nos dois aliases, GET/HEAD e com/sem barra final. Hashes das fontes e do build privado do contador continuam iguais ao recibo aceite; provas anteriores reutilizadas, não repetidas.
Permanecem separados: **instalação conjunta pendente**, **revisão visual pendente** e **melhorias da janela S08 por concluir**. Contexto preservado no redireccionamento não demonstra a funcionalidade da S08 nem encerra a revisão visual. Sem instalação, publicação ou alteração de dados/configurações.
## 10/11/2026 — contagem única e cobertura multiterritorial preparadas
O total conta cada oportunidade pública uma única vez, mesmo com vários territórios. O indicador de províncias conta localizações declaradas distintas: uma oportunidade em duas províncias corresponde a uma no total e duas na cobertura. Localização desconhecida mantém a oportunidade no total sem inventar província. Abrangência nacional e contexto de corredor não se convertem automaticamente em implantação.
[Código, critérios e provas](../v2/auditoria-portal/revisao-externa-2026-11-10.md): nove testes novos/23 asserções passaram; cache versionada, tipos, lint e compilação privada verificados. **O contador e os aliases anteriores estão preparados/testados, sem disponibilização no portal. Revisão visual e melhorias da janela S08 continuam pendentes, com provas aceites preservadas.** Nenhuma prova histórica do simulador/Balcão/aliases/S08 foi repetida ou tomada como conclusão nova. Sem dados de negócio alterados, migrations ou deploy.
## 10/11/2026 — entradas antigas do simulador e dos contactos
Os endereços `/simulador` e `/contactos`, também com barra final, passam a redireccionar permanentemente para `/simulador-fiscal` e `/apoio`. Os parâmetros de consulta são conservados; os formulários e permissões dos destinos existentes mantêm-se. O alias anterior `/Simulador` permanece disponível.
[Relatório da revisão externa](../v2/auditoria-portal/revisao-externa-2026-11-10.md): nove testes dirigidos/37 asserções passaram sem acesso à base ou email real. O teste do router não é uma demonstração visual ou HTTP ponta a ponta. A contagem multiterritorial e a revisão desktop/móvel continuam pendentes; não se alteraram dados nem regras fiscais. Reversão limitada ao ciclo de aliases em `routes/web.php`, sem rollback de schema.
## 02/11/2026 - reconciliacao das pre-condicoes da suite ampla
A classificacao das falhas foi confrontada com os testes e com a configuracao efectiva. Os quatro 403 do intake de negocios pertenciam ao percurso de listas legadas, mas herdavam a activacao de perfis do ambiente. Com variante legada explicita, passaram nove testes/248 assercoes do intake e das operacoes documentais reais; GD/WebP foi activado apenas pelo argumento PHP -d extension=gd, sem alterar o runtime partilhado. O proprio ExistingBusinessIntakeTest passou a declarar essa variante no setUp, conservando todas as recusas de isolamento, MFA, designacao, auto-aprovacao e estados. Verificacao sem flags externas de perfis: sete testes/154 assercoes passaram. Nenhuma politica do produto foi alargada.
@@ -9184,3 +9584,15 @@ Delta verificado: 12 testes frontend dirigidos aprovados, tipos globais, lint do
**ACT-02 permanece parcial.** Cobertura factual nacional/regional, séries comparáveis, oferta laboral e metodologia quantitativa de potencial económico continuam pendentes conforme fontes aplicáveis. O cenário sintético não é estatística real. Conferência autenticada de Competitividade pela equipa e requisitos amplos V2-07/V2-10 preservados. Sem push, instalação deste delta, migração, importação, publicação automática, escrita activa, alteração de permissões ou reinício de serviços.
=======
>>>>>>> 705dc69d683a451eda1371db7eceb280047b2895
### 10/11/2026 ? commits separados; S08 e visual pendentes
Ajustes locais separados por ?mbito na branch `entrega/directiva-20261110`: pacote contador/aliases `377c40a2`; contactos `4e614802`; informa??o/SEO `4e07557c`; pr?-dossier `dcecd251`; curadoria `7f131497`; sitemap `252f38bb`. Cobertura, limites e hashes em `docs/investmoz/v2/auditoria-portal/ajustes-por-ambito-2026-11-10.md`. Provas aceites preservadas. Sem push ou instala??o.
S08 permanece por concluir, separada da revis?o visual; a especifica??o da janela n?o foi recuperada e n?o ? substitu?da pela etapa de licenciamento hom?nima. Visual interrompido por impossibilidade da ferramenta de confirmar o URL, n?o por Escape nem por falha demonstrada do portal. Cluster QA 55441 mantido ligado enquanto houver depend?ncias por conferir. Nenhum requisito amplo ? promovido a conclu?do por estes commits.
### 10/11/2026 ? S08: melhoria do percurso existente de licenciamento pr?vio
?mbito esclarecido pela Tempo; substitui o anterior saldo ?por identificar?. Implementa??o e sete provas frontend dirigidas em `67344418`: estado sem repeti??o, registo/ac??o principal vis?veis, redu??o dos blocos expans?veis e abertura/foco da etapa por `#S08`, preservando campos/contexto/vers?o/grava??o e guardas existentes. TypeScript, lint dirigido e build QA aprovados. Sem altera??o de backend/hist?rico/dados ou repeti??o das provas aceites n?o afectadas. Detalhes e limites em `docs/investmoz/v2/auditoria-portal/ajustes-por-ambito-2026-11-10.md`. Revis?o visual e instala??o mant?m-se pendentes e separadas. Pacote contador/aliases preservado; sem push e cluster 55441 mantido ligado.
@@ -0,0 +1,75 @@
# Ajustes separados por ?mbito ? 10/11/2026
Estado: commits locais na branch `entrega/directiva-20261110`, sobre main `2209b838`. Sem push, instala??o ou activa??o. A directiva permanece parcial. Provas anteriores conservadas; testes n?o equivalem a aceita??o ou revis?o visual.
| Commit | ?mbito | Cobertura dirigida j? executada | Pend?ncia |
| --- | --- | --- | --- |
| `377c40a2` | Contador e aliases: ?rvore exacta do pacote preparado | Contador/cache 9 testes/23 asser??es; aliases/contexto conforme recibo selado, incluindo 8 casos/56 asser??es de contexto | Instala??o e visual |
| `4e614802` | Operador Tempo, Lda. e contacto institucional existente | 2 testes frontend de apresenta??o/valor partilhado | Visual; nenhum contacto dedicado inventado |
| `4e07557c` | Resumo p?blico/SEO e data de verifica??o distinta de ausente/inv?lida | 2 testes frontend de informa??o | Visual; preenchimento factual continua dependente dos dados |
| `dcecd251` | Pr?-dossier imprim?vel a partir da projec??o autorizada existente | 3 testes/16 asser??es; 5 testes cliente de estado; QA 200 do titular/403 de outro actor, projec??o e registo preservados | Visual, impress?o/PDF real e percurso de equipa |
| `7f131497` | Destaque editorial expl?cito, pr?via, confirma??o, motivo cifrado e hist?rico paginado | 3 testes/21 asser??es de autoridade; 4 testes cliente; QA legado 11 verifica??es; publicador nativo/MFA; hist?rico 25+1 | Visual e instala??o; sem escolha/publica??o autom?tica |
| `252f38bb` | Sitemap p?blico, origem fixa e p?ginas limitadas | 4 testes/19 asser??es; QA compara 51 oportunidades com o scope p?blico | Visual n?o aplic?vel ao XML; publica??o/indexa??o e instala??o pendentes |
A ?rvore do primeiro commit ? `4c9334ffd750555a97fbd8ad29301a3954f74e76`, id?ntica ao manifesto do pacote. ZIP preservado: SHA256 `7aec8f2795dc44690fcc4cfb2c436db4d9b589d0112bd266e517b84074870213`. Os commits posteriores n?o alteram esse artefacto. O sitemap e a curadoria partilham apenas o ficheiro de rotas, com altera??es separadas nos respectivos commits.
## Limites das provas
Build final QA aprovado (2813 m?dulos, 37,56 s), PHPStan sem erros e TypeScript `tsc --noEmit` aprovado. Build QA privado: n?o foi instalado e n?o ? artefacto de produ??o. As provas anteriores s?o reutilizadas, sem repetir suites gerais.
QA exclusiva `main_visual_20261104_test`, porta 55441. Curadoria: transac??es revertidas; actores sint?ticos existentes, sem novas concess?es. Prepara??o MFA sint?tica revertida. Hist?rico 25+1 usa 26 notas sint?ticas, n?o decis?es de neg?cio. Registos/actor e contagem de auditoria restabelecidos; sequ?ncias de auditoria podem avan?ar apesar de rollback. Pr?-dossier n?o executa opera??es nem confirma elegibilidade fiscal.
A exist?ncia antiga de `is_featured` no editor legado n?o demonstrava disponibilidade na gest?o nova: o controlador legado bloqueia grava??o quando esta est? instalada. O commit de curadoria cobre essa lacuna sem alterar a autoridade nativa de publica??o, o estado de publica??o ou o contexto do workflow.
## S08 e visual: saldo conservado
**A melhoria da janela S08 n?o est? conclu?da.** Os documentos recuperados referem a pend?ncia sem especifica??o execut?vel. A etapa de licenciamento tamb?m chamada S08 n?o ? usada como substituto. Foi solicitada somente a refer?ncia da conversa/ficheiro j? fornecido para recuperar o requisito, n?o uma nova defini??o. Nenhum dos commits ou testes acima encerra S08.
**A revis?o visual n?o est? conclu?da.** Causa correcta da interrup??o: a ferramenta n?o conseguiu confirmar com confian?a o URL actual no Windows; n?o foi a tecla Escape e n?o constitui falha demonstrada do portal. Retomar quando o acesso estiver dispon?vel ou pela equipa. Conferir desktop/mobile, foco/teclado, header/contador e aliases com contexto, apoio/rodap?, ficha/data, pr?-dossier/impress?o e curadoria (pr?via, confirma??o, recupera??o, pagina??o e altera??es por gravar). Preservar provas aceites anteriores; registar ambiente, commit e resultado de cada percurso.
O cluster 55441 permanece ligado. Existem servidor de desenvolvimento e workers do portal em execu??o; aus?ncia moment?nea de liga??es n?o prova aus?ncia de depend?ncias. N?o foi encerrado nem reiniciado qualquer servi?o. Instala??o continua pendente e separada.
## Recibos locais preservados
- `outputs/directive-implementation-20261110/curation-qa.json` ? SHA256 `114e90597c03e17c318c4f52d6af83dac60bb74599db6f37b12c4bd735f4531a`.
- `outputs/directive-implementation-20261110/native-curation-qa.json` ? SHA256 `65cf7aff819d00fb3b23e5567be06caf3cb7683049126ae2eacd17c5df9babaa`.
- `outputs/directive-implementation-20261110/curation-history-qa.json` ? SHA256 `df25fde75f17ac3eb46eb65266e8ec62f1048432803cb8e4aa404d2787cdd7e7`.
- `outputs/directive-implementation-20261110/workspace-qa.json` ? SHA256 `ef20640b25689e0c54e9906efe54846d2401487b633b57bf42f650e2ecafe520`.
- `outputs/directive-implementation-20261110/build-final.log` ? SHA256 `167daca7b4284a7fe652a472a631a551fce5455d2364a9f6a783320bc962d798`.
## Recuperacao das fontes Portal V4
A Tempo indicou a pasta `Portal V4` em resposta ao pedido de referencia. Inventariados dez DOCX e `cartografia_moz.xlsx`; originais preservados. Texto dos dez DOCX conferido, sem referencia literal a S08, S-08 ou S8. A imagem incorporada, identica em seis documentos, e um diagrama de entidades CRM, sem numeracao de janelas. Isto nao prova que o requisito nao foi transmitido; falta ligar a designacao S08 ao titulo ou URL da interface. Foi solicitada apenas essa identificacao. Nenhuma alteracao funcional foi atribuida por inferencia a S08.
`correccoes a introduzir no portal.docx` descreve ajustes de formulario, perfil/contacto, pesquisa contextual, localizacao, estrutura financeira, emprego, operacao fiscal, necessidades, documentos e revisao, alem de catalogo, procedimentos e parcerias. Estes ambitos nao identificam inequivocamente uma janela S08 e nao autorizam declarar todo esse conjunto como o seu saldo. Documentos de referencia nao substituem as decisoes expressas da Tempo ou as provas aceites.
Hashes dos originais inventariados:
- `Matriz detalhada de oportunidades por sector.docx`: `73d7f7792631d144d69616061354b88fcbbedce825d0af533d5317ed34c8ccd6`.
- `Portal de investimentos v4.docx`: `a0438b98c56ce930453f3eb519a952b3274c01fc421b2a2b4c5a994e988a84bb`.
- `upgrade do portal de investimentos v6 programacao.docx`: `e1169a7743b13ff6c950d3ffd4672a2afea32e81da84885e11154b7d8de9241e`.
- `upgrade do portal de investimentos -casos .docx`: `b44ec86d313eee6790be68a829f43e00c21e191c16cb89b4f8c37dbddc26d0ba`.
- `correcções a introduzir no portal.docx`: `bfb24adfdc43afac8492b30b6bb2b7c57d5a3d57c7cd36008d9fdd5a80cd7dc5`.
- `cartografia_moz.xlsx`: `2c5728c46973866371139cb080b1b1837bb980a43ae91693f0b58f2263a6c455`.
- `simulador fiscal.docx`: `55997917b3bd85727cffac880b6c46fc85a78fef6a005798b9a110a0bca60fd0`.
- `upgrade do portal de investimentos v1.docx`: `f70ce3075523c6278e00dce15b9c7acf2b690679e16959ae5f7d007e754cfc6e`.
- `upgrade do portal de investimentos v2.docx`: `51b8f0ad3296d1064a5c12b450874fc72640869f10a164319cd7f3153dd3a086`.
- `upgrade do portal de investimentos v5 programacao.docx`: `edece80c0519537244f8a8b86456af0aa92de1c526f791bb0ee9b85af7a7e8b2`.
- `simulador fiscal v1.docx`: `f038d338fa0247ddd70f9dd34e72b080f9b7c93acfc02aec0d433bca1ee606d2`.
### Esclarecimento da Tempo sobre S08
Em resposta ao pedido do titulo/URL da janela, a Tempo informou que nao dispoe dessa referencia adicional. Encerra-se esse pedido de esclarecimento, sem exigir novamente a mesma informacao. S08 permanece no saldo como pendencia de ambito por identificar; nao ha criterio executavel suficiente para lhe atribuir alteracoes de codigo ou provas de conclusao. Nao e declarada resolvida, cancelada ou equivalente a etapa de licenciamento homonima. Os commits preparados e as provas aceites mantem-se; revisao visual e instalacao continuam pendentes e separadas. Cluster QA 55441 mantido ligado.
## S08 ? ?mbito esclarecido e incremento implementado
A Tempo esclareceu expressamente que S08 ? a melhoria do percurso existente de **licenciamento pr?vio**. Esta decis?o substitui as anota??es anteriores de ?mbito por identificar; n?o ? uma funcionalidade nova. Commit de c?digo e provas: `6734441855059d01bd087b323ee87ce03631d9ba`, separado do pacote contador/aliases e dos restantes ajustes.
S08 mostra o estado actual uma vez no t?tulo; o selector de estado mant?m a fun??o de edi??o. A etapa passa a sec??o de largura completa, com formul?rio e refer?ncia vis?veis, retirando tr?s blocos expans?veis internos. A fase conserva a organiza??o do percurso. O bot?o ?Guardar registo? recebe destaque e mant?m o bloqueio durante a grava??o. Entrada inicial e mudan?a de fragmento `#S08` abrem os ancestrais da etapa, deslocam e d?o foco ? etapa existente. N?o h? nova navega??o, altera??o da query, remount dos campos, selec??o de outra oportunidade ou confirma??o autom?tica. Alvos desconhecidos, ausentes ou exteriores ao percurso n?o produzem efeito.
Contrato de grava??o preservado: mesmo endpoint e transforma??o, vers?o actual do dossier, oportunidade, revis?o e confer?ncia de contexto, estado/ac??o, fundamento, refer?ncia, documento, entidade e data; mesmos campos condicionais de revis?o, erros e `preserveScroll`. Guardas `preparation_allowed`, `canWrite` e `canReview` conservadas. Nenhum controlador, servi?o de neg?cio, pol?tica, schema, hist?rico ou dado foi alterado neste incremento. As restantes etapas conservam os seus blocos expans?veis; a abertura por fragmento usa os alvos existentes.
**Verifica??o dirigida:** sete testes frontend aprovados: estado/ac??o vis?veis, grava??o/contexto/vers?o/campos, leitura/revis?o e processamento/erros, preserva??o das outras etapas, abertura/foco, alvos inv?lidos e efeito inicial/hashchange/remo??o do listener. TypeScript e lint dirigido aprovados; build QA privado aprovado. Os testes usam harness de componentes e elementos simulados: n?o constituem revis?o visual, navegador real nem nova prova HTTP das autoriza??es do servidor. N?o foi usada qualquer base ou repetida a suite aceite do backend. Recibo `outputs/s08-licensing-20261110/verification.json` e log `outputs/s08-licensing-build-20261110.log`.
Matriz existente: anotada apenas AUD-LEG-07 nas colunas de fonte/evid?ncia, com 3555 linhas e todas as c?lulas anteriores preservadas. Nenhum requisito amplo promovido a conclu?do. **Implementa??o e provas dirigidas deste recorte conclu?das; revis?o visual e instala??o continuam pendentes e separadas.** Conferir pela equipa ou quando o acesso permitir: entrada directa e hashchange, desktop/m?vel, foco/teclado, campos por gravar e grava??o/erro com os perfis existentes. A ferramenta n?o conseguiu confirmar o URL; isso n?o demonstra falha do portal. Sem push, instala??o ou encerramento do cluster QA 55441.
@@ -1,3 +1,368 @@
## 11/11/2026 — apresentação pública profissional do directório financeiro em QA
**Pedido do proprietário:** descrições profissionais e apelativas,com descrição/produtos/serviços úteis ao investidor,sem texto de fichas,importação ou documento gerador;orientação transversal registada emAGENTS. O directório e o detalhe passam a uma apresentação editorial comum:reconhece exclusivamente o resumo genérico exacto da importação e usa a natureza publicada da entidade;descrições editoriais específicas são preservadas e limpas da fórmula genérica final. Produtos identificados são apresentados pelos nomes/instrumentos existentes;os sete catálogos específicos disponíveis não são extrapolados para outras entidades. Nas restantes,apresentam-se áreas de serviço conhecidas ou contacto para conhecer soluções,sem inventar produtos/condições.
Retirados da apresentação pública nomes de ficheiros,hashes,linhas de importação,códigos de sucessora e descrições de registo interno. Ligações institucionais e datas úteis continuam acessíveis;metadados e fontes permanecem intactos no cadastro/editor. Instituições históricas/fundidas continuam identificadas como tal. Instituições sem chamada não mostram uma secção vazia de candidatura. Textos da selecção guardada e chamadas públicas dePorque investir/Serviços ajustados à linguagem do investidor. Fluxos de interesse,dossier,edição,revisão/publicação e filtros preservados.
**QA:** nove verificações afectadas aprovadas,três novas incluindo270referências fornecidas,descrições específicas preservadas,produtos não inventados e renderização dos componentes públicos reais de lista/detalhe. Sem escrita em dados activos ou nova publicação do lote;é uma projecção editorial dos dados já publicados. Evidência `outputs/funding-public-editorial-20261111/verification.json` e quatroHTMLs;isto não constitui conferência visual no navegador. Tipos/lint/compilação aprovados;pacote Git com aplicação/reversão conferidas no fecho.
**Estado:** implementado em QA,não instalado;homologação mantém `8f295f05` e270instituições publicadas. Restam instalação/visual e enriquecimento de produtos específicos quando houver informação disponível. O saldo dos outros módulos e resíduos fiscais permanece inalterado. Não é necessário seleccionar ou publicar novamente as270instituições para esta alteração de apresentação.
## 11/11/2026 — rectificação do volume PEC instalada em8f295f05
Instalação expressamente autorizada,executada pelo processo Git existente e conferida às11:03:24Africa/Johannesburg(09:03:24UTC): `8f295f05510c786afeb8ea1dfa692e08d90a0d30`. No simulador fiscal,L8,**Rectificar volume na simulação PEC** conserva o valor original e o corrigido,com escolha explícita/fonte e efeito na estimativa apresentados no relatório. A hipótese de tesouraria anterior exige reconciliação se o total mudar;nenhum comprovativo pago,dedução ou saldo oficial é duplicado. Não executa pagamentos nem submete declaração rectificativa.
Backup novo/restauro isolado conferidos:190tabelas/369ficheiros. Quinze fontes por hash;revisão,entrada,up,login,Concursos,fundos e simuladorHTTP200. Configuração intacta,zero migrações/importações. Totais/checksums idênticos ao backup nas sete tabelas protegidas:funding_programmes,funding_programme_versions,funding_programme_interests,desk_cases,simulation_form_drafts,labour_demands,training_capacities.275fichas/270instituições publicadas preservadas. Recibo `outputs/irpc-pec-correction-20261111/installation.json`;pacote `outputs/entrega-pec-rectificacao-20261111.zip`,SHA256 `559e4477059b092a193d990f7dfb6cb50007d43c24ec00e2484326f076e0c973`. Revisãoa708f079 preservada para reversão.
Este recorte29(3) deixa de ser pendência de implementação/instalação. Visual permanece separado;correcções fora do recorte têm âmbito próprio. Restam última base29(5),PPC excedente,períodos/regimes especiais e demais resíduos L1–L7/restanteL8/L9–L16 e módulos. Próximo trabalho:primeiro residual de simulação executável com as fontes aceites,sem reabrir este recorte. Portal não declarado integralmente concluído.
## 11/11/2026 — rectificação do volume PEC na simulação em QA
**Recorte29(3) avançado:** fonte documental permite rectificar o volume de negócios2025 usado na estimativa PEC2026. Valor original,valor corrigido,titular,exercício e fundamento ficam conservados;escolha explícita entre aplicar a correcção e manter o original. Não submete declaração rectificativa. A estimativa recalcula0,5%,limites e PPC anteriores uma única vez;relatório mostra origem,valor corrigido e efeito na estimativa. Mudança da base/titular/exercício exige reconferência;fonte ou valor ausente não é zero.
Cálculo→API privada→recuperação→HTML/relatório do mesmo dossier em cinco cenários:aumento40.000→90.000MZN,redução40.000→20.000,original conservado40.000,base divergente e correcção para volumezero indeterminados. Hipótese de caixa anterior40.000 deixa de reconciliar quando o total muda;não é sobrescrita. Saldo anual138MZN,comprovativos pagos e histórico anterior preservados. QA55441 existente:uma prova nativa/47asserções com rollback,cinco testes novos de cálculo/interface e40afectados aprovados;tipos/lint/compilação aprovados. Provas `outputs/irpc-pec-correction-20261111/*-verification.json`;pacote Git com aplicação/reversão conferidas no fecho. Não é conferência visual.
**Estado:** implementado em QA,ainda não instalado;homologação mantém `a708f079` e270instituições publicadas. Fonte:RIRPC,Decreto9/2008,art29(3),BR16/I16-04-2008,original já aceite SHA256 `fc373ff5637edf75ec6a9595dec8763a8503d4ee786e3b7d8f1c742eae9d80ee`. Sem migração,base,tabela ou direito novo. Restam instalação/visual,última base29(5),PPC excedente,períodos/regimes especiais e demais resíduos fiscais L1–L7/restanteL8/L9–L16. Correcções fora deste recorte conservam âmbito próprio;L8 e portal não são declarados integralmente concluídos. Próximo trabalho:primeiro residual de simulação executável pelas fontes aceites,sem reabrir este recorte.
## 11/11/2026 — finalidade de viabilidade e projecção PEC instaladas ema708f079
Instalação expressamente autorizada,executada pelo processo Git existente e conferida às10:45:50Africa/Johannesburg(08:45:50UTC): `a708f079b318a13100644c27287f1caa854ecf1d`. No simulador fiscal,L8,os ecrãs apresentam **Simular PPC/PEC**,estimativas fiscais e hipóteses de tesouraria para avaliar a viabilidade do negócio. A hipótese PEC permite distribuir valores por três meses ou concentrar a estimativa num mês escolhido,com soma conferida e prazo legal não certificado. Rascunho,recuperação,histórico e relatórios preservam escolhas;dados documentais já pagos permanecem distintos. O portal não executa pagamentos,nem submete declarações fiscais.
Backup novo/restauro isolado conferidos:190tabelas/369ficheiros. Vinte e quatro fontes por hash;revisão,entrada,up,login,Concursos,fundos e simuladorHTTP200. Configuração intacta,zero migrações/importações. Totais/checksums idênticos ao backup nas sete tabelas protegidas:funding_programmes,funding_programme_versions,funding_programme_interests,desk_cases,simulation_form_drafts,labour_demands,training_capacities.275fichas/270instituições publicadas preservadas. Recibo `outputs/irpc-pec-plan-20261111/installation.json`;pacote `outputs/entrega-simulacao-viabilidade-20261111.zip`,SHA256 `a4dbbe26ca72230e3ff7a5c2a744c187a93380a4857e522b1db5ee17acce3ca5`. Revisãobcbbe51c preservada para reversão.
Este delta deixa de ser pendência de implementação/instalação. Conferência visual permanece separada. Execução de pagamentos não é critério de conclusão deste simulador,conforme clarificação expressa registada emAGENTS. Próximo trabalho:primeiro residual de simulação executável pelas fontes aceites. Base29(5),rectificação29(3),PPC excedente,períodos/regimes e restantes resíduos L1–L7/restanteL8/L9–L16 e módulos mantêm o saldo;portal não declarado integralmente concluído.
## 11/11/2026 — finalidade do simulador e projecção de tesouraria PEC
**Clarificação expressa do proprietário:** o portal simula impostos para avaliar a viabilidade do negócio; não faz pagamentos. A apresentação anterior de «obrigação» e «plano de pagamento» criava uma expectativa errada de execução. Ecrãs PPC/PEC passam a **Simular PPC/PEC**,estimativa fiscal e projecção de tesouraria. Fontes legais e dados documentais já pagos continuam a informar o cálculo; não são operações executadas pelo portal. A execução de pagamentos não é pendência deste simulador.
**Implementado em QA:** hipótese PEC distribuída por Junho/Agosto/Outubro com montantes explícitos cuja soma corresponde exactamente ao total,ou hipótese concentrada num mês escolhido. Não inventa igualdade/arredondamento ou validação de prazo para a opção única. Valores desconhecidos,divergência da soma,total alterado e ausência de estimativa positiva invalidam a projecção dependente,sem bloquear o cálculo fiscal. Trocar hipótese conserva edições;resumo com Alterar. Rascunho privado,recuperação,histórico e relatório do mesmo dossier reutilizados. As estimativas não alimentam os comprovativos pagos nem alteram deduções anuais.
Cinco verificações novas de cálculo/interface e35afectadas aprovadas;percurso sintéticoAPI/recuperação/relatório em quatro cenários(distribuído,concentrado,divergente,total alterado),com saldo anual138MZN conservado. Provas `outputs/irpc-pec-plan-20261111/*-verification.json`. Sem migração,base,papel ou permissão nova. Tipos/lint/compilação aprovados;uma prova nativa/38asserções com rollback. Pacote Git e reversão conferidos no fecho. Isto não é conferência visual.
**Estado:** QA,ainda sem instalação;homologação permanece `bcbbe51c` e270instituições publicadas. Restam instalação/visual e recortes fiscais de base29(5),rectificação29(3),PPC excedente,períodos/regimes e outras regras de simulação;prazo legal da hipótese única não é certificado. L1–L7/restanteL8/L9–L16 e demais módulos permanecem no saldo,sem transformar o portal em sistema de execução fiscal. Próximo trabalho:primeiro residual de simulação executável com fontes aceites.
## 11/11/2026 — total e calendário PEC instalados embcbbe51c
Instalação expressamente autorizada,executada pelo processo Git existente e conferida às10:28:18Africa/Johannesburg(08:28:18UTC): `bcbbe51c2fc279d5b2d85412f7bcb6ca2423a9b6`. Utilizável no simulador fiscal,fecho anual L8: **Preparar obrigação PEC**,com total ordinário2026 baseado no volume documental2025,limites antes dos PPC pagos anteriores,mesesJunho/Agosto/Outubro e dispensa no ano de início efectivo. Guardar/recuperar pelo rascunho privado e apresentar no relatório do mesmo dossier. Não cria pagamento,dedução anual ou reembolso.
Backup novo/restauro isolado conferidos:190tabelas/369ficheiros. Catorze fontes por hash;revisão,entrada,up,login,Concursos,fundos e simuladorHTTP200. Configuração preservada,zero migrações/importações. Sete tabelas protegidas mantêm totais/checksums do backup:funding_programmes,funding_programme_versions,funding_programme_interests,desk_cases,simulation_form_drafts,labour_demands,training_capacities.275fichas/270instituições publicadas preservadas. Recibo `outputs/irpc-pec-obligation-20261111/installation.json`;pacote preservado `outputs/entrega-pec-obrigacao-revisada-20261111.zip`,SHA256 `a9d9bb089305003487695969d653c7f876a913fce44bbbffb5191c1a792a9184`. Revisãofbff2660 preservada para reversão.
Este recorte deixa de ser pendência de implementação/instalação;visual permanece separado. Restam montantes por prestação/pagamento único,última base29(5),rectificação29(3),PPC excedente,períodos/regimes especiais,guias/juros. Próximo trabalho:primeiro desses casos executável pelas fontes aceites,sem repetir o recorte instalado. L1–L7/restanteL8/L9–L16 e restantes módulos continuam no saldo;portal não declarado integralmente concluído.
## 11/11/2026 — total e calendário PEC ordinário em QA
**Novo recorte L8:** preparação do total PEC2026 pelo volume de negócios documental2025:0,5%,limites30.000/100.000MZN aplicados antes de deduzir PPC efectivamente pagos no ano anterior. MesesJunho/Agosto/Outubro,sem inventar dia limite ou importar o arredondamento PPC. Dispensa do ano de início efectivo da actividade29(4) fundamentada separadamente. Fonte:RIRPC,Decreto9/2008,art29(1–5),BR16/I16-04-2008,original SHA256 `fc373ff5637edf75ec6a9595dec8763a8503d4ee786e3b7d8f1c742eae9d80ee`, já preservado no acervo.
Formulário progressivo reutiliza titular/exercício e conserva valores ao ocultar campos sem aplicação; rascunho privado e relatório do mesmo dossier reutilizados. Total preparado não é PEC pago,nem cria dedução anual,reembolso,guia ou consumo de saldo oficial. Ausência de informação não equivale a zero. Sem volume no ano anterior exige base29(5);PPC superiores ao montante limitado ficam por reconciliar,sem obrigação negativa. Esses casos não impedem o cálculo anual independente.
**Percurso sintético:** cálculo→API privadaPOST/PUT→GET→recálculo→HTML/relatório:40.000MZN ordinário,20.000mínimo antes de PPC,90.000máximo antes de PPC,dispensa inicial0,volumezero e desconhecido indeterminados. Histórico e saldo anual138MZN preservados. QA55441 existente,uma prova nativa/56asserções com rollback,sem nova base/tabela/porta/papel/migração. Seis verificações novas de cálculo/interface e29afectadas aprovadas; tipos/lint/compilação aprovados. Provas `outputs/irpc-pec-obligation-20261111/*-verification.json`;pacote Git com aplicação/reversão conferidas no fecho.
**Saldo:** implementado em QA,ainda não instalado. Homologação mantém `fbff2660` e270instituições publicadas. Restam entrega Git e visual;montantes por prestação/opção de pagamento único,última base29(5),rectificação29(3),PPC acima do limite,períodos/regimes especiais,guias/juros permanecem abertos. L8 não é integralmente concluído;resíduos L1–L7/restanteL8/L9–L16 e restantes módulos conservados. Próximo trabalho:primeiro desses casos executável com as fontes aceites,sem reabrir os recortes entregues.
## 11/11/2026 — obrigação PPC instalada emfbff2660
Instalação expressamente autorizada e executada pelo processo Git existente: `fbff2660f280f53fe079221164bb21917032ac70`, conferida às09:04:59Africa/Johannesburg(07:04:59UTC). Utilizável no simulador fiscal,fecho anual L8: **Preparar obrigação PPC**,com imposto anterior documental líquido de retenções,80%,três prestações arredondadas individualmente por excesso ao milhar e calendárioMaio/Julho/Setembro. Guarda e recuperação pelo rascunho privado existente e inclusão no relatório do dossier. Obrigação preparada não equivale a pagamento,nem cria dedução de PPC.
Backup novo/restauro isolado conferidos:190tabelas/369ficheiros. Catorze fontes do delta por hash e revisão,entrada,up,login,Concursos,fundos e simuladorHTTP200. Configuração preservada,zero migrações/importações;sete tabelas protegidas com totais/checksums idênticos ao backup:funding_programmes,funding_programme_versions,funding_programme_interests,desk_cases,simulation_form_drafts,labour_demands,training_capacities.275fichas/270instituições publicadas preservadas. Recibo `outputs/irpc-ppc-obligation-20261111/installation.json`; pacote `outputs/entrega-ppc-obrigacao-20261111.zip`,SHA256 `ab72a11b9306e4503ab92669c47ec592b2510aa01189ebab1d5216bb40d3769a`. Revisão6805b606 preservada para reversão.
Este recorte deixa de ser pendência de implementação/instalação. Conferência visual permanece separada. Próximo residual fiscal:PEC/obrigações e outros casos executáveis com fontes aceites;anualização/períodos especiais,dispensas/guias/juros e resíduos L1–L7/restanteL8/L9–L16 mantêm-se explícitos. Restantes módulos permanecem no saldo existente;portal não declarado integralmente concluído.
## 11/11/2026 — obrigação PPC ordinária de L8 em QA
**Pendência avançada:** o fecho anual existente passa a preparar a obrigação PPC2026 com liquidação documental do exercício civil completo2025: imposto anterior21(1) líquido de retenções,80%,três prestações iguais arredondadas individualmente por excesso ao milhar de meticais. Calendário por mês:Maio,Julho,Setembro,sem inventar dia limite. Fonte:RIRPC,Decreto9/2008,arts27(1)(a)/28(1–3),BR16/I16-04-2008,original SHA256 `fc373ff5637edf75ec6a9595dec8763a8503d4ee786e3b7d8f1c742eae9d80ee`, já preservado no acervo. Não usa colecta projectada corrente como liquidação anterior.
Reutiliza titular/exercício e rascunho privado existentes; formulário progressivo conserva edições deliberadas ao trocar enquadramento. Obrigação preparada e comprovativos efectivamente pagos permanecem distintos: não cria pagamento,dedução,saldo oficial,guia ou juros. Campos vazios,fonte ausente,divergência de titular/período,base negativa e período especial ficam indeterminados,sem impedir o fecho anual independente. Zero explícito permanece zero. Anualização28(3),dispensas,PEC e restantes regimes ficam separados.
**Prova sintética:** cálculo→POST/PUT→GET privado→recálculo→HTML/relatório do mesmo dossier em cinco cenários:72.000MZN ordinário,75.000após arredondamento,zero,valor desconhecido e período especial indeterminados. Saldo anual138MZN e comprovativos inalterados; histórico anterior conservado. Uma prova nativa/47asserções com rollback no QA55441 existente; cinco verificações novas de cálculo/interface e24afectadas aprovadas. Tipos/lint/compilação aprovados; pacote Git com aplicação/reversão conferidas no fecho. Provas `outputs/irpc-ppc-obligation-20261111/*-verification.json`.
**Estado:** implementado em QA, ainda sem instalação. Homologação mantém `6805b606`,Educação entregue e270instituições publicadas. Restam entrega Git deste delta e visual; L8 não está integralmente concluído. Preservam-se os resíduos L1–L7,restanteL8/L9–L16 e restantes módulos. Próximo trabalho fiscal: PEC/obrigações e demais casos executáveis com as fontes aceites,sem reabrir PPC ordinário.
## 11/11/2026 — retoma da revisão de Educação instalada em6805b606
Instalação autorizada pelo utilizador, executada pelo processo Git existente e conferida às08:46:05Africa/Johannesburg(06:46:05UTC): `6805b606a106fbed410b191ed778c0fad7c8e913`. Guardar e recuperar a preparação privada da revisão interna de procura/capacidade fica utilizável na homologação, pelo detalhe existente em `/management/education`. A gravação privada não decide nem aprova; preserva notas, versão analisada e a chave de uma intenção incerta.
Backup novo e restauro isolado conferidos:190tabelas/369ficheiros. Catorze fontes conferidas por hash; revisão,entrada,up,login,Concursos,fundos e simuladorHTTP200. Configuração intacta, zero migrações/importações. Totais e checksums inalterados nas sete tabelas protegidas:funding_programmes,funding_programme_versions,funding_programme_interests,desk_cases,simulation_form_drafts,labour_demands,training_capacities. Permanecem275fichas/270instituições publicadas. Recibo `outputs/education-review-preparation-20261111/installation.json`; pacote preservado `outputs/entrega-educacao-retoma-20261111.zip`, SHA256 `d2b7fd5e0d24baffd7b53d534c78e6e0f4492b47a40bb1d9d146b8a8b553caf5`.
Este recorte deixa de ser pendência de implementação e instalação. Conferência visual continua pendente; jornadas externas educativas e representação/consentimento documentados permanecem em aberto. Resíduos fiscais L1–L7/restanteL8/L9–L16 e restantes módulos mantêm-se no saldo existente. Próximo trabalho: primeiro residual executável dessa ordem, sem repetir este recorte nem as provas aceites.
## 11/11/2026 — retoma durável da revisão de Educação em QA
A revisão interna de procura e capacidade pode guardar e recuperar a preparação privada, incluindo notas, versão analisada e intenção de decisão incerta. Reutiliza os eventos de auditoria existentes, com conteúdo cifrado e isolamento por conta, tipo e declaração. Guardar não decide: a decisão continua pelo escritor nativo, com revisão independente e validação de versão. A intenção fica persistida antes do envio; uma resposta perdida recupera a mesma chave, sem duplicar a decisão. Edições posteriores e preparações mais recentes noutra janela não são substituídas silenciosamente.
**QA concluído, ainda não instalado:** cinco testes nativos/105 asserções e dez testes novos de interface, com os 17 afectados preservados; análise PHP e tipos aprovados. A página nativa recuperada foi exercitada no componente real em harness: recibo reconhecido, edição posterior conservada e zero envios adicionais. Prova `outputs/education-review-preparation-20261111/verification.json`. Isto não constitui conferência visual no navegador. Sem migração, tabela, papel ou concessão nova.
**Saldo:** falta entregar este delta pelo processo Git e conferir a apresentação no uso normal. Jornadas externas de estudantes, formadoras e empregadores, representação e consentimento documentados permanecem pendentes; Educação não é declarada integralmente concluída. A homologação mantém `9c067d6a` e as 270 instituições publicadas. Permanecem explícitos os resíduos fiscais L1–L7/restante L8/L9–L16 e os restantes módulos. As provas aceites não foram reabertas.
## 11/11/2026 — combinação CBF15/créditos/PEC instalada em9c067d6a
Instalação expressamente autorizada nesta retoma e executada pelo processoGit existente: `9c067d6ae89c0cd1e9ad4f2813e84048988d3319`, conferida às07:43:38Africa/Johannesburg(05:43:38UTC). Dezficheiros do delta conferidos por hash; revisão,entrada,login,up,Concursos,fundos e simuladorHTTP200; configuração preservada, zero migrações/importações. Backup/restauro isolado conferido:190tabelas e369ficheiros. Nenhum restauro sobre dados activos.
**Utilizável na homologação:** combinação CBF15corrente/transportado com créditos económicos/internacionais e saldos anteriores, com escolha dePEC limitada à colecta residual apósCBF15; gravação/recuperação e relatórios pelo percurso existente, conforme cinco cenáriosQA aceites. Os recortes anteriores de transporte e os históricos são preservados. Nenhum saldo oficial é consumido, pagamento/reembolso executado ou nova prioridade entre exercícios inventada.
**Preservação activa comprovada:** totais e checksums idênticos ao backup nas tabelas `funding_programmes`, `funding_programme_versions`, `funding_programme_interests`, `desk_cases` e `simulation_form_drafts`.275fichas,270publicadas; o original financeiro continua disponível. Não se extrapola esta comparação para tabelas voláteis de sessão/cache. Prova `outputs/irpc-cbf15-combination-20261111/installation.json`; pacote e reversão `outputs/entrega-irpc-combinacao-20261111.zip`, SHA256 `cebcd063b06f70a33b57f6cdd710478fb7aa3d18ef64aec4489cf855064bff99`.
Saldo: este recorte deixa de ser pendência de implementação/instalação. Conferência visual mantém-se separada; continuam repartição automática/múltiplos projectos, outros períodos/moedas/regimes/EP/cumulações/benefícios/formulários e os restantes resíduos L1–L7/restanteL8/L9–L16 e módulos não fiscais. O portal não é declarado integralmente concluído. A próxima implementação seguirá o primeiro residual executável do saldo existente, sem reabrir a combinação entregue nem a publicação das270instituições.
## 11/11/2026 — combinação CBF15 com créditos económicos/internacionais e PEC em QA
Retoma da ordem de conclusão existente, sem nova auditoria ou repetição da publicação financeira:270instituições publicadas e revisão instalada4f3dbcf8 preservadas; visual financeiro/S08 permanece separado. Reconciliação delimitada: o transporte CBF15 básico(7476dc34) já integra a revisão instalada4f3dbcf8, pelo histórico Git e recibos aceites; a nota antiga «QA, não instalado» desse transporte foi superada por entregas posteriores. Não reinstalar esse recorte como se estivesse ausente.
**Implementado nesta passagem, ainda sem instalação:** retirada a recusa técnica genérica da combinação de CBF15 corrente/transportado com saldos anteriores, reutilizando as fontes e ordem de dedução existentes. Créditos económicos e internacionais/correspondentes transportes precedemCBF15; PEC corrente e anterior usam apenas a colecta residual seguinte, antes de retenções/PPC. Uma escolha explícita de PEC superior ao residual é recusada como indeterminada, sem reduzir silenciosamente parcelas ou fabricar saldo recuperável. Histórico e fontes de investimento/uso anterior continuam preservados; a chave da combinação inclui versão própria para exigir recálculo dos resultados anteriores desse ramo, sem reescrever históricos nem invalidar os recortes sem combinação.
Cinco cenários sintéticos: combinação ordinária138MZN; quota residual documental100→saldo438; PEC sobrealocado→saldo indeterminado; CBF15 esgota colecta1338 ePEC seleccionado expressamente0→saldo−500 proveniente dePPC, sem reembolso/consumo oficial; janelaCBF15 expirada→saldo538. Também provada a varianteCBF15 corrente e o limite de quota após créditos anteriores. Cálculo→POST/PUT do rascunho privado existente→GET→recálculo exacto→HTML/dossier aprovados no QA55441/proactive_investment_20261027_test, com rollback e sem novas bases/portas/migrations/direitos. Uma provaAPI/37asserções; cinco testes novos nas combinações e23casos afectados de compatibilidade aprovados, não multiplicados como funcionalidades. Tipos/lint aprovados; compilação QA e pacote registados no fecho. Provas `outputs/irpc-cbf15-combination-20261111/*-verification.json` e relatórios recuperados.
**Saldo:** implementação e percurso desta combinação concluídos emQA; instalação do novo delta e visual separados. Persistem repartição automática/múltiplos projectos, outros períodos/moedas/regimes/EP/cumulações/benefícios e documentos fora dos recortes demonstrados; L1–L7/restanteL8/L9–L16 não são encerrados. Os cinco programas anteriores privados do directório financeiro não são publicados por consequência desta entrega. Próximo trabalho: agrupar a entrega compatível deste delta e continuar o primeiro residual fiscal executável, preservando os fechos aceites.
## 10/11/2026 —270instituições publicadas e consultáveis na homologação
**Cadastro e disponibilização do documento fornecido concluídos em homologação.** Conferência de leitura às22:54:31Africa/Johannesburg(20:54:31UTC), revisão instalada `4f3dbcf86499180011fc79712d6b5be445e57e26`:270/270instituições/operadores do documento publicados, nenhuma referência do original em falta,270identidades únicas,393referências físicas e168contactos. Publicações confirmadas pelo fluxo editorial nativo do utilizador; zero escritas efectuadas pelo verificador. As275fichas totais incluem cinco referências anteriores de fundos/programas que permanecem privadas, sem serem publicadas por consequência deste lote. Fonte original/SHA/edição, incertezas e identidades preservadas.
Consulta pública comprovada porHTTP200 e dados efectivamente devolvidos: `/fundos?kind=institution&include_historical=1` contém270resultados; filtro categoria seguros/tipo seguradora/presença física/Nampula contém seis(Fidelidade,Emose,HollardMoçambique,Britam,Arko,Palma). Fichas deBCI,Absa eArko abrem no componente público e conservam a fonte SHA256 `119bb5514d2777849b70aebfec21d28a41885cbdc7d9827fbebd764fe994c108`. A opção de incluir histórico não transforma uma instituição fundida/extinta em activa. Sede/balcões continuam separados de cobertura e produtos; dados desconhecidos não se tornam condições confirmadas nem garantias de financiamento/seguro.
**Saldo ACT-20261029-12 neste recorte:** conteúdo do documento cadastrado, publicado pelo editor e disponível por localização/categoria/tipo; já não é página vazia ou apenas códigoQA. Continuidade banco/seguradora/balcão/fonte/versão no mesmo dossier, gravação/recuperação e conservação após alterações continuam cobertas pelas provasQA aceites, reutilizadas sem criar dossier sintético em produção. Conferência visual de navegador permanece separada; não se presume instalação de outros módulos, fecho integral do portal ou conclusão dos resíduos fiscais. Nenhuma nova instalação/alteração de direitos exigida para declarar este resultado. Prova `outputs/funding-directory-20261110/institutions/publication-complete.json`.
## 10/11/2026 — recusa depois do login: navegadores e sessões diferentes
URL comunicado pelo utilizador:`/dashboard`. Conferência dirigida dos registos confirma login/MFA no **Edge** às22:03:53, seguido de`/dashboard`HTTP200 às22:03:55. A abertura seguinte de`/management/fundos/referencias-assistidas`às22:04:02/22:04:17 recebeu403 em **Safari/WebKit**, não no Edge. Existiam duas sessões da mesma conta: a sessão recente do Edge com confirmaçãoMFA válida e a anterior com confirmação antiga. A autorização editorial/verificação de email/perfil existentes estavam preservados. Logo esta nova recusa não comprova expiração do MFA recém-concluído no Edge nem falta de permissão da conta; o link abriu outro contexto de navegador.
Solução indicada: copiar e colar o URL do editor na barra do Edge já autenticado, mantendo a mesma aba nas retomas. Não repetir login por rotina, alterar permissões, desactivar verificações ou reinstalar por este motivo. **Reposição de acesso confirmada:** GETdo editor noEdge às22:11:21(+02),HTTP200. A recusa de entrada ficou delimitada e resolvida por uso da sessão correcta, sem nova instalação, concessão ou alteração de segurança. A instalação4f3dbcf8 permanece; publicação/conteúdo consultável ainda não declarados concluídos. Consulta estritamente de leitura; nenhum identificador de sessão/token/cookie/palavra-passe exportado e nenhuma sessão modificada. Prova:`outputs/funding-directory-20261110/institutions/access-context-diagnosis.json`.
## 10/11/2026 — retoma editorial instalada em4f3dbcf8
Correcção instalada pelo executor Git existente em `4f3dbcf86499180011fc79712d6b5be445e57e26`, confirmada às22:01:41Africa/Johannesburg (20:01:41UTC).7ficheiros conferidos por hash; revisão/entrada/login/up/Concursos/fundos HTTP200, configuração preservada, zero migrações/importações automáticas. Novo backup/restauro190tabelas/369ficheiros conferido, sem substituir dados activos. Pacote `outputs/entrega-instituicoes-retoma-20261110.zip`, SHA256 `9bce0250d546cd87f7c60a55f0c1d47fba74e8965f7d3224a588e140f87ec3cb`; anteriores preservados.
**Saldo activo:90rascunhos, zero publicações.** A indicação de publicação não foi confirmada no servidor por expiraçãoMFA/403. A correcção torna visível o estado e conserva os recibos na mesma aba, poractor/fontes; não publica por consequência da instalação nem altera o limiteMFA900s. Os90rascunhos anteriores ao armazenamento no cliente são recuperados idempotentemente na primeira passagem actualizada; após isso as fichas conferidas são saltadas nas retomas da mesma aba. Confirmações de revisão/publicação voltam a ser exigidas na entrada, e o backend revalida guardas/hash/versão. Fechar a aba/limpar armazenamento não elimina rascunhos, mas perde a retoma local. Estado alterado por outra janela exige conferência actual; o armazenamento do cliente não é prova de publicação.
Resta a conclusão autenticada da preparação/publicação e a consulta efectiva do conteúdo no destino. ACT12 continua aberto, visual pendente, sem reabrir APIEX/SWOT/S08 ou resíduos fiscais anteriores. Prova: `outputs/funding-directory-20261110/institutions/resume-installed.json`.
## 10/11/2026 — retoma após MFA, sem repetir fichas conferidas
Verificação activa posterior:90rascunhos/zero publicações; continuação GETMomentum403 às21:49:39 ePOSTpublicar403 às21:49:54 (+02). A sessão voltou a expirar. A publicação comunicada não chegou a ser executada pelo servidor. Nenhum rascunho foi removido.
Correcção delimitada emQA: a mesma aba conserva selecção, recibos/versionamento/hash e fundamento, separados por conta e conjunto de fontes; após reautenticação salta fichas já conferidas. As confirmações de revisão/publicação são desmarcadas na nova entrada, sem publicação automática. Uma publicação de resposta incerta conserva o mesmo corpo para recuperação idempotente. Novo comando de renovação utiliza logout/login/MFA nativos; limite900s e guardas mantidos. «Seleccionar todas as instituições(270)», contagem de progresso e controlos antes da lista recolhida tornam o estado visível. Sem migração, dados novos ou alterações de permissões. Alteração ainda não instalada nesta anotação; revisão activaef24cbdc.
Prova dirigida do componente efectivo: remonta a página como após autenticação, recupera selecção/recibos, não repete preparação, não publica automaticamente, recupera confirmação incerta e isola outra conta/fontes. Prova API editorial anterior1caso/27asserções reexecutada apenas pela propriedadeeditor_id acrescentada; provas270fichas e do dossier reutilizadas. Tipos/análise/compilação da entrega são registados pelo executor existente.
## 10/11/2026 — confirmação da preparação parcial e expiração do MFA
Após execução no editor, a homologação contém **32rascunhos e zero fichas publicadas**. A preparação gravou efectivamente e avançou por diferentes instituições. Os registos HTTP mostram preparação200 atéMyBucks às21:31:42, seguida deGETxtenda403 às21:31:43 ePOSTpublicar403 às21:31:59 (Africa/Johannesburg). A entrada no editor fora confirmada às21:16:43; corresponde ao limite existente de900segundos do MFA em `PortalProfileAccess::mfa`. Não é perda da instalação nem repetição de dados. Reautenticar no portal com MFA e retomar a preparação idempotente conserva os32rascunhos, sem substituir edições; publicação continua a exigir a confirmação separada. Nenhuma guarda, sessão ou permissão foi alterada. Revisão instalada `ef24cbdc`, ACT12 ainda sem conteúdo público confirmado. Prova de leitura: `outputs/funding-directory-20261110/institutions/publication-observation.json`.
## 10/11/2026 — instalação confirmada do directório financeiro
Revisão `ef24cbdc9218e2027f7d1a39968cc60869a35410` instalada na homologação às19:15UTC, pelo executor Git existente.33ficheiros conferidos por hash com o commit; configuração preservada, zero migrações/importações automáticas. Backup/restauro confirmado de190tabelas e369ficheiros. Entradas `/fundos?kind=institution`, `/concursos`, `/login`, `/up` e revisão HTTP200. Pacote anterior preservado; nova entrega `outputs/entrega-instituicoes-editorial-20261110.zip` SHA256 `168d7ffb6d054533674f3f07799959b46bc12641c045accfc1766da5cead9077`.
**Implementação instalada; conteúdo ainda sem publicação confirmada.** Leitura activa:zero fichas/zero publicações; tabelas sem RLS, logo o resultado não decorre de falta de contexto de leitura. Entrada autenticada no editor assistido confirmada nos registos; ainda sem pedidos POST de preparação/publicação observados. A indicação do utilizador de operação concluída permanece por reconciliar com o resultado efectivo, sem repetir a importação ou simular MFA. A conta existente tem designação editorial e perfil exigidos; nenhuma concessão alterada. ACT-20261029-12 permanece aberto até existirem fichas consultáveis. Visual e restantes resíduos do portal mantidos.
## 10/11/2026 — preparação editorial assistida das instituições
A autenticação SSH foi confirmada, mas não substitui a sessão e MFA recentes do editor no portal. Acrescentada `/management/fundos/referencias-assistidas`: selecção explícita das instituições do documento, conferência das fontes, preparação sequencial idempotente e publicação confirmada numa segunda operação. Preparar nunca publica. Fichas deliberadamente alteradas são preservadas e exigem revisão individual; mudança do conjunto de fontes interrompe a operação. Uma resposta de publicação perdida conserva versão/hash/fundamento para a mesma repetição. Expiração de MFA mantém as guardas e os resultados já confirmados.
Prova adicional delimitada:1caso API/27asserções,1caso frontend de separação preparação/publicação e recuperação da confirmação. Reutilizadas as5provas API/735asserções e28frontend anteriores; não se repetiu a carga das270instituições. Instalação e publicação no destino continuam distintas e serão registadas após confirmação efectiva. Sem migrações, novos direitos ou alteração das sessões existentes.
## 10/11/2026 — instituições por localização, categoria e tipo no processo do investidor
Pedido expresso da Tempo: inserir as instituições do documento agora indicado por localização/categoria/tipo e permitir que o investidor consulte bancos, seguradoras e outros intervenientes quando a necessidade surgir, sem limitar a decisão por falta de informação. **Implementação e conteúdo demonstrados em QA; nenhuma instalação ou publicação activa executada nesta passagem. Última revisão instalada confirmada:1c75a30a, APIEX/SWOT/Concursos preservados. ACT-12 não é encerrado pela preparação técnica.**
Original read-only `Notas/Instituições financeiras em Moçambique.docx`, edição Novembro2026, SHA256 `119bb5514d2777849b70aebfec21d28a41885cbdc7d9827fbebd764fe994c108`. Texto,509linhas de tabelas e nomes/contactos presentes nos parágrafos reconciliados. **270instituições/operadores,393referências de localização física e168referências de contacto**, não270ofertas de financiamento nem393moradas independentemente confirmadas. Instituições repetidas são reunidas por identidade explícita/aliases documentados ou número de registo citado; divergências, moradas alternativas, terceiros, dados históricos e identidades por confirmar permanecem identificados, sem substituir factos. Entidades sem morada continuam consultáveis como não localizadas. Totais estatísticos/redes incompletas não criam entidades ou agências fictícias. Listas antigas não confirmam licença actual; ABC permanece fundida/extinta segundo o documento, ligada à referência Access, sem atribuir a origem dos balcões por inferência.
Reutilizado o catálogo/versionamento/editorial existente: categorias banca,crédito/microfinanças,gestão de investimento,pensões,seguros/resseguros,mediação,câmbio,pagamentos e supervisão; uma entidade pode ter mais de uma categoria/função sem duplicação. Filtros por categoria,tipo e presença física são distintos da cobertura do produto. Província do cadastro é associada apenas por correspondência inequívoca; rua/localidade e precisão limitada conservam-se como declaradas. Pesquisa por cidade/balcão/morada/alias permite localizar informação textual (por exemplo Beira), sem geocodificação inventada. Informação por confirmar não é correspondência confirmada; ausência de registo não prova ausência de oferta. Oito gestoras de pensões continuam distintas de produtos de crédito, e seguradora não é apresentada automaticamente como financiadora/garantia de crédito.
**Continuidade no mesmo dossier:** ferramentas do investimento abrem a pesquisa contextual; seleccionam-se necessidade (incluindo seguros), instituição e, opcionalmente, sede/balcão. Guarda-se identidade estável, categorias/tipos, versão pública, local exacto escolhido, fonte/documento/SHA/linha, estado de confirmação e produto quando existente. Banco e seguradora coexistem; gravação repetida não duplica escolha. Actualização posterior dos pressupostos do investimento conserva a escolha; retirada da ficha pública não apaga a fotografia consultada. Dados/etapas/autoridade de decisão anteriores não são alterados. Consulta pública livre, operações privadas e edição editorial com as guardas existentes; nenhuma candidatura, email ou contacto enviado às instituições, novo perfil/grant/porta/schema ou ligação fictícia.
Provas no QA55441 existente, com rollback: **5casos API/735asserções únicas** (2desta fonte/608 e3de compatibilidade/127),28frontend dirigidos incluindo S08/navegação e recuperação da tentativa com balcão escolhido, tipos/PHPStan/lint/Pint/compilação QA aprovados. Uma tentativa de suite paralela foi recusada pela guarda da mesma base; compatibilidade executada sequencialmente sem remover a guarda ou criar outra base. `outputs/funding-directory-20261110/institutions/{content,verification}.json` e **`catalogo-qa.html` com270fichas ligadas**; renderização estática dos componentes, não conferência visual de navegador. Provas e pacote anteriores das11fichas/BNI Capital/FDEL mantidos. As7instituições comuns são enriquecidas pela mesma identidade; não há segundo directório. Fontes/documento original não foram alterados.
**Saldo concreto:** conteúdo e percurso local/categoria/tipo → banco/seguradora → fonte/contacto → mesmo dossier → continuação/recuperação demonstrados em QA. Restam instalação e disponibilização/revisão/publicação autenticada pelo fluxo editorial existente, consulta efectiva no destino e visual (conector indisponível). Preparação documental não publica lote automaticamente, conformeAGENTS.md; não declarar informação já disponível na homologação por existir este código. Restantes módulos e resíduosL1–L7/restanteL8/L9–L16 mantêm o saldo anterior.
## 10/11/2026 — ACT-20261029-12: directório financeiro com conteúdo em QA
**Homologação mantém1c75a30a; este conjunto não está instalado nem publicado.** A leitura autenticada do destino confirmou catálogo /fundos HTTP200, schema instalado e zero fichas/versões. O requisito não fica encerrado por essa disponibilidade técnica. APIEX/SWOT mantêm a entrega instalada própria, sem reabertura.
Localizada e reutilizada a lista fornecida em `Relatorios/benchmark de melhorias investeMoz.docx`, SHA256 `065203aacfae766c62cf9c2c272ebacc61ff23bd4ff9e91535939fede87f7617`, quadro de04/10/2026. Doze entradas documentais correspondem a **11fichas:7instituições e4programas/fundos** — BCI,Millennium bim,Absa,Standard Bank,Moza,BNI,Gapi,FECOP,Semeia Negócios,FCID VIII eFAE; **BNI Capital** integra os produtos do BNI, sem duplicar instituição ou presumir personalidade jurídica autónoma. Agregado genérico de linhas BNI não gera fundos fictícios. Referência FDEL anterior conservada, sem a contar novamente nesta entrega.
O cadastro/versionamento/competência editorial existentes passam a conservar natureza factual, identidade estável, contactos com fonte/data, locais físicos (sede/balcão/ponto institucional) e produtos próprios. Moradas não determinam cobertura financeira. Produto tem finalidade, instrumento, condições/montantes, disponibilidade e cobertura próprios; vazios são identificados como por confirmar. Cobertura e necessidade têm de corresponder ao **mesmo produto**, antes da paginaçãoSQL. Critério documentado pode excluir desconhecidos; consulta geral distingue resultados por confirmar de correspondência sustentada. FCID conserva seis províncias documentadas, sem confundir Maputo Província/Cidade; FAE mantém o Vale do Zambeze como âmbito textual com vínculo geográfico por confirmar, sem inventar distritos. A localização institucional do BNI não limita o financiamento a Maputo. Semeia conserva a chamada encerrada; a antiga linha BCI2018 não é reaberta. Condições e janelas históricas não se tornam ofertas actuais pela importação. Fontes indicadas consultadas para âmbito/disponibilidade; falha de recolha da Gapi não revoga o original, cuja data04/10 e disponibilidade por confirmar ficam explícitas.
Demonstrado em QA55441 existente: localização/necessidade → consulta das fichas reais fornecidas → fonte/contacto/condições → escolha privada no mesmo dossier e oportunidade → recuperação. Snapshot preserva versão pública e produto consultados mesmo após retirada/alteração da ficha. Repetição da intenção e recuperação após recarregar não duplicam a escolha/evento; outra oportunidade/actor e dossier encerrado mantêm guardas. Não envia candidatura, contactos ou dados às instituições, não concede crédito nem classifica o investidor automaticamente. Sem pontuação/IA, schema/base/porta nova, novas competências ou alteração de dados activos.
Provas dirigidas: **3casos API/127asserções e13frontend**, PHPStan nível7 sem supressões, tipos/lint/Pint/compilação QA aprovados. Testes transaccionais revertidos; sequências QA podem avançar. `outputs/funding-directory-20261110/verification.json`, `content-qa.json`, `dossier-qa.json`; **catálogo consultável de revisão** `outputs/funding-directory-20261110/catalogo-qa.html` e11fichasHTML ligadas. Renderização estática dos componentes afectados, não conferência visual de navegador nem publicação no destino. Provas anteriores preservadas e reutilizadas.
**Saldo ACT-12:** recorte funcional e conteúdo fornecido demonstrados em QA, preparado para disponibilização pelo fluxo editorial. **Restam instalação do conjunto, preparação/revisão/publicação autenticada das11fichas no destino e confirmação da consulta real pelo investidor**; visual continua pendente com conector indisponível. Não declarar ACT-12 encerrado ou o portal integral. Dados de sede/balcões, condições ou cobertura não documentados conservam o estado por confirmar; não bloqueiam os restantes dados fundamentados. Resíduos L1–L7/restanteL8/L9–L16 e outros módulos mantêm o saldo anterior.
## 10/11/2026 — APIEX, SWOT e acesso destacado a Concursos instalados
**Homologação instalada e conferida: `1c75a30a601ef00fc187ddf5a64183446d329e6f`**, https://teste-investidor.tempo.co.mz. Inclui APIEX/SWOT37854d02 e correcção do menu autorizada pela Tempo. **Concursos e anúncios** tem agora grupo próprio no menu horizontal comum, com consulta pública e acompanhamentos para utilizadores autenticados; endereço https://teste-investidor.tempo.co.mz/concursos. O módulo já estava activo e apenas a entrada estava dentro de Descoberta; não foi necessário activá-lo novamente. Um rascunho privado e zero versões publicadas preservados.
Conferência final:21hashes Git iguais à release, revisão HTTPS correcta e root/up/login/concursos HTTP200, configuração intacta, zero migrations/importações. Backup fresco restaurado com190tabelas/contagens/checksums iguais e369ficheiros/configuração conferidos;7476dc34 e pacotes anteriores preservados para reversão de código. Recibos `outputs/apiex-swot-concursos-delivery-20261110/{backup,restore,files-restore,verification,delivery}.json`; verificação UTC no recibo. Instalação executada pelo mesmo executor Git, sem alterações de permissões, dados reais, publicação de análises ou submissão externa.
**Saldo:** APIEX e SWOT abaixo deixam de ser pendência de instalação. Acesso pelas ferramentas **Preparação APIEX e SWOT** do dossier existente, conservando a oportunidade seleccionada. Conferência visual no uso normal continua pendente no mesmo registo; não é inferida das provas automáticas. Âmbitos normativos não mapeados continuam indeterminados; os restantes módulos e resíduos L1–L7/restanteL8/L9–L16 conservam o saldo anterior. O portal não é declarado integralmente concluído.
## 10/11/2026 — correcção dirigida das âncoras antes da promoção
O executor recusou c7901592 por um caso frontendS08 (1falha/1788), mantendo7476dc34 activo. Diagnóstico delimitado: a página tinha dois efeitos de âncora e o teste existente conservava uma única sequência de dependências. A abertura SWOT foi integrada no listener de hash já existente, também para mudanças posteriores de #tarefas, sem alterar a guarda/foco/expansão de S08. O teste S08 aceite conserva as suas asserções; acrescentado caso que alterna #tarefas/#S08, verifica isolamento no dossier e limpeza do listener.15casos dirigidos APIEX/SWOT/âncoras aprovados. Nenhuma alteração de dados, permissões, fontes normativas ou configuração; entrega autorizada retomada no mesmo mecanismo, sem mascarar a falha nem desactivar verificações. Recibos finais continuam a determinar a revisão instalada.
## 10/11/2026 — Concursos visíveis no menu principal; instalação agrupada autorizada
Conferência delimitada do destino7476dc34 por SSH autenticado: `procurement.enabled=true`, módulo disponível, tabelas presentes; `/concursos` e raiz HTTP200 com `procurementEnabled=true`. Preservados um concurso em rascunho e zero versões publicadas. O módulo não desapareceu nem estava desligado: a ligação estava dentro de Descoberta. Pedido Tempo «onde foram parar concurso? depois de resolver isso autorizo a instalação»: entrada promovida a grupo próprio **Concursos e anúncios** no menu horizontal comum, com **Consultar concursos e anúncios** e, após autenticação, **Os concursos que acompanho**. Retirada só a ligação duplicada de Descoberta; cinco atalhos principais e autorizações editoriais preservados. Não cria/publica concursos nem muda flags, dados ou schema.
Prova dirigida da navegação:20casos aprovados, incluindo visitante/utilizador, disponibilidade, destinos únicos e selecção correcta de catálogo/ficha/acompanhamentos. Tipos/lint/compilação QA aprovados. Observação anterior `outputs/investment-preparation-20261110/concursos-before.json`; pacote agrupado `outputs/entrega-apiex-swot-concursos-20261110.zip`, preservando o pacote APIEX/SWOT anterior. O conjunto APIEX/SWOT abaixo conserva as provas aceites, sem repetição. Instalação autorizada expressamente após esta correcção; execução e recibo final separados desta prova QA. Visual continua pendente por indisponibilidade do conector.
## 10/11/2026 — APIEX e SWOT no dossier existente: QA concluído, instalação pendente
**Instalado:** `7476dc343098f15a7982e9d7581bf4f59d43bc1a` em https://teste-investidor.tempo.co.mz, promovido às14:28:12UTC e conferido às14:29:07UTC (16:29 locais). Transporte CBF15 deixa de ser pendência de instalação. Delta14hashes Git/HTTPS, root/up/login/revision200, configuração intacta, zero migrations/importações; backup/restauro190tabelas e369ficheiros conferidos. Recibos `outputs/cbf15-carry-delivery-20261110/`; release anterior e pacotes preservados.
**Implementado em QA, ainda não instalado — APIEX:** dossier existente → requisitos fundamentados disponíveis/em falta/não aplicáveis/indeterminados → documentos existentes e complementos → revisão → prévia/ZIP com índice e anexos íntegros → registo da entrega externa, esclarecimento, resposta e decisão com data/referência/comprovativo. Dados do investidor/empresa/contexto/localização/oportunidade reutilizados. Estados em preparação/pronto para entrega/entrega registada/decisão recebida distintos; exportar não submete nem aprova. Snapshot preparado, versões, SHA e pacote privado cifrado preservados; recuperação devolve os mesmos bytes após alteração do contexto, revalidando acesso e segurança dos documentos. Ausência de recepção electrónica APIEX, segundo a orientação Tempo, não bloqueia este percurso; nenhum conector fictício ou formulário oficial inventado. Reutiliza o avaliador e modelos/fontes aceites; períodos/regimes fora do âmbito actualmente mapeado ficam indeterminados, sem conclusão normativa presumida.
**Implementado em QA, ainda não instalado — SWOT:** no mesmo dossier/oportunidade, objecto explícito investimento/território/sector/cluster, evidências do contexto/análise proactiva e Competitividade pública aprovada, quatro dimensões internas/externas, fonte/data/classificação de cada conclusão (facto documentado/hipótese/por confirmar). Sem pontuação, IA ou inferência de ausência pela falta de registos. Gravação/recuperação/histórico paginado e revisão pela equipa com permissões existentes. Alteração/retirada de fontes assinala revisão, conserva fotografia consultada e edições deliberadas. Acções ideia/oportunidade/parceiros/risco continuam no mesmo dossier; risco liga à tarefa nativa com origem própria e repetição sem duplicação. Privado por defeito; publicação exige o fluxo editorial existente, nunca automática. Contextos retirados ficam no histórico privado de leitura, sem reactivação.
Provas sintéticas no QA55441 existente, com rollback: seis casos API/211asserções;16casos frontend dirigidos (incluindo transporte e cabeçalho comum147páginas), tipos, análise estática e lint aprovados. `outputs/investment-preparation-20261110/verification.json`, ZIP/índice/anexos, recuperação SWOT e provas de isolamento/histórico/segurança. Pacote Git agrupado `outputs/entrega-apiex-swot-20261110.zip`, base exacta7476dc34, reversão de código sem apagar auditoria, pacotes privados ou tarefas posteriores. Nenhuma migration/base/porta/conta real nova, submissão real ou publicação. Compilação QA separada; provas anteriores reutilizadas.
**Saldo concreto:** APIEX e SWOT acima encerrados em implementação QA; instalação deste conjunto e conferência visual continuam pendentes. O conector mantém “The browser access host is unavailable”; SSR/testes não substituem visual, sem novo ciclo de reinícios. Os restantes módulos conservam o saldo anterior, incluindo resíduos L1–L7, restanteL8 e L9–L16. Isto não declara o portal integralmente concluído.
## 10/11/2026 — transporte documental CBF15 em QA
Homologação mantém `d216830b`; este delta ainda não está instalado. Tipos/lint/Pint/buildQA aprovados; pacote com aplicação/reversão exactas,sem migrations/dados/credenciais. Pendência encerrada no recorte normal residente2026/ano civil/MZN: crédito de investimento de origem anterior do mesmo contribuinte/projecto/local/regime, com activo/documento/parcela, exercício de origem, NUIT/projecto de origem, taxa original e qualificação históricas documentados. Exercício de origem anterior a2026,dentro da janela original e não anterior à aquisição; no projecto em funcionamento,a aquisição não precede o início aplicável. Não renova o prazo nem recalcula origem a outra taxa. Valor corrente/ausência e uso efectivo agregado exigem reconciliação explícita; histórico de simulações não equivale a utilização fiscal.
Cálculo → POST/PUT do mesmo rascunho privado → GET → recálculo → HTML/dossier demonstrados no QA55441 existente com rollback. Exemplo sintético recuperado: nominal de origem4.000,uso efectivo anterior1.000,disponível3.000,quota documental do projecto200 → dedução200,não utilizado2.800,saldo500MZN. Início2022 → quinto exercício2026; mudando o início para2021 → sexto,parcela expirada3.000,disponível/dedução0,saldo700,com activo,uso anterior e origens preservados. Seis/sete resultados históricos conservados,incluindo o resultado ordinário aceite. Provas `outputs/irpc-cbf15-carry-20261110/{carry,expired}-verification.json` e relatórios respectivos; API um caso/28asserções novas,30casos frontend nas fronteiras alteradas incluindo formulário real compilado e prevenção de duplicação. SSR/formulário simulado não é navegador/visual. FonteCBF15(4/7),APIEX2025 PDF71–72 já aceite,reutilizada.
A preparação mantém os activos correntes e exige explicitar os dados em falta; activos de origem e correntes partilham a capacidade documental e a detecção de duplicação entre mapas. Origem com outro NUIT/projecto/taxa,exercício incoerente,uso superior ao nominal ou declaração de ausência incompatível não produz dedução. Sem pagamento,reembolso,consumo oficial ou criação automática de saldo transportado. Livros antigos permanecem nos formatos1/2; só o transporte preparado usa livro3/detalhe2,para o código anterior recusar em vez de ignorar o crédito novo. Snapshots e históricos aceites não são reescritos; reversão é de código e não deve sobrescrever dados posteriores.
Saldo preservado: outros projectos/localizações/regimes/cumulações/EP/L7,combinação positiva com transportes económicos/internacionais/PEC anteriores,repartição automática,benefícios/formulários oficiais e restantes resíduos L1–L7/restanteL8/L9–L16 mantêm os seus recortes. Os módulos não fiscais mantêm o saldo existente,sem nova auditoria geral. Próximo trabalho: primeira combinação residual executável respeitando a ordem de dedução e quota do projecto,ou percurso independente do saldo existente; visual continua pendente no mesmo registo. Não é conclusão integral do portal.
## 10/11/2026 — menus horizontais instalados
**Homologação: `d216830b068af8a7a40d43993795639e73aaed78`**, promovida às13:56:27UTC e conferida às13:56:54UTC (15:56 locais), em https://teste-investidor.tempo.co.mz. Delta10hashes Git e revisão HTTPS iguais; root/up/login/revision HTTP200, configuração preservada, zero migrations/importações. Backup/restauro190tabelas com contagens/checksums iguais e369ficheiros/configuração conferidos; revisão d5cb41b1 preservada para reversão. Recibos `outputs/horizontal-menu-delivery-20261110/{backup,restore,files-restore,verification,delivery}.json`. Pacote anterior e dados/históricos preservados; sessãoSSH pronta para continuação autenticada, sem credencial em disco.
O recorte dos menus horizontais abaixo deixa de ser pendência de instalação: cabeçalho comum em páginas públicas/autenticação/definições/privadas, sem sidebar global concorrente; Descoberta sem repetição dos cinco atalhos mantidos na entrada principal. Prova estrutural dos146layouts e23casos dirigidos reutilizada; não é conferência visual. O conector continua indisponível,sem novos reinícios ou alterações ao navegador. Visual mantém-se pendente no mesmo registo. O restante saldo de funcionalidades por módulo e L1–L7/restanteL8/L9–L16 não é encerrado por esta instalação.
## 10/11/2026 — janela CBF15 instalada; menus horizontais uniformizados em QA
**Homologação: `d5cb41b111cac95c431a3072b8c4cc0141481ec2`**, promovida às13:43:40UTC e conferida às13:44:18UTC (15:44 locais). Delta12hashes Git e revisão HTTPS iguais; root/up/login/revision HTTP200, configuração preservada, zero migrations/importações. Backup/restauro190tabelas e369ficheiros/configuração conferidos,89e6d72c preservada. Recibos `outputs/cbf15-window-delivery-20261110/`. Janela de cinco exercícios CBF15 deixa de ser pendência de instalação; transporte de créditos anteriores e restantes resíduos fiscais continuam separados.
**Delta posterior em QA, ainda não instalado — pedido expresso da Tempo10/11, corrigido para menus horizontais com dropdown:** cabeçalho comum passa a ser montado uma vez pelo layout global em todas as páginas; área privada usa o mesmo cabeçalho sem navegação lateral concorrente. Autenticação, definições, gestão e páginas públicas conservam conteúdo e breadcrumbs; filtros de capacidades, direitos e destinos privados existentes preservados. Esconder cabeçalho na impressão mantém-se. Os cinco atalhos (Porquê investir,Partir de uma ideia,Consultar oportunidades,Simulador Fiscal,Sectores) ficam intactos na entrada principal. Descoberta conserva localizações/mapa,catálogos e módulos opcionais,sem repetir os atalhos; Partir de uma ideia já estava fora desse grupo. CSS responsivo e implementação comum dos dropdowns/teclado/Escape/toque reutilizados,sem novo menu alternativo.
Conferência estrutural dos layouts das146páginasTSX existentes: exactamente um cabeçalho comum por composição, incluindo páginas privadas,autenticação/definições, sem sidebar global; conteúdo/contexto/breadcrumbs e cinco atalhos preservados. Provas `tests/frontend/portal-horizontal-shell.test.mjs`, navegação e renderização dirigida (23casos aprovados). É SSR com dependências de UI substituídas,não prova visual de navegador. Tentativa única de inventárioCUA nesta continuação devolveu exactamente `Browsers: Error: The browser access host is unavailable`; nenhuma alteração/reinício de navegador. Conferência visual mantém-se pendente no registo existente. Tipos/lint/build QA aprovados, logs desta entrega; base activa/dados/permissões intocados.
O saldo integral dos restantes módulos e L1–L7/restanteL8/L9–L16 permanece no plano existente; nenhuma contagem de páginas/testes declara o portal concluído.
## 10/11/2026 — janela de cinco exercícios CBF15 em QA
Homologação mantém `89e6d72c`; o delta seguinte ainda não está instalado. Pendência encerrada em QA: contagem inclusiva dos cinco exercícios civis do CBF15(4/7) na liquidação2026, para investimento efectivo do próprio2026. Início de exploração para projecto novo; início dos procedimentos após aprovação para projecto em funcionamento. Não usa cinco anos móveis nem reinicia o prazo pela data de aquisição. Início2022, mesmo em31/12 → quinto exercício2026, dedução200 pela quota residual, saldo500MZN. Início2021 → sexto exercício2026, prazo expirado, dedução0 e saldo700; activo e documento preservados. Datas inválidas/futuras e activos antigos apresentados como investimento corrente permanecem indeterminados/recusados, não zero por omissão.
Cálculo → POST/PUT do mesmo rascunho privado → GET → recálculo → HTML/dossier demonstrados no QA55441 existente, com rollback, cinco/seis resultados históricos e todos os anteriores preservados. Provas `outputs/irpc-cbf15-window-20261110/{fifth,expired}-verification.json` e relatórios respectivos. Modelo `IRPC-CBF15/2`, livro existente versão2 sem migration; snapshots aceites não são reescritos. Fonte já aceite APIEX2025 PDF71–72,CBF15(4/7), reutilizada sem nova validação de autenticidade. Provas anteriores reutilizadas; apenas fronteiras alteradas conferidas.
Saldo: transporte de créditos de investimento de anos anteriores, repartição automática/múltiplos projectos, EP/L7/cumulações/outros benefícios/formulários oficiais continuam por implementar nos respectivos recortes. A janela corrente não declara o transporte entregue. Resíduos L1–L7, restanteL8 e L9–L16 e saldo dos módulos não fiscais preservados. Conferência visual pendente no mesmo registo; portal não integral. Próximo trabalho executável: transporte documental CBF15 dentro da mesma janela, com consumo anterior reconciliado e sem duplicar o investimento corrente.
## 10/11/2026 — CBF15 instalado na homologação
**Revisão instalada: `89e6d72c1e86df8eb86e7be74583659975585036`**, promovida às13:27:59UTC e conferida às13:28:21UTC (15:28 locais), em https://teste-investidor.tempo.co.mz. Os17hashes do delta coincidem com Git; revisão/root/up/login HTTP200, configuração preservada, zero migrations/importações. Backup fresco/restauro190tabelas com contagens/checksums iguais e369ficheiros/configuração extraídos e conferidos;7c8d4726 preservada para reversão. Recibos `outputs/cbf15-delivery-20261110/{backup,restore,files-restore,verification,delivery}.json`; pacote imutável `outputs/entrega-cbf15-20261110.zip`, SHA25613bdecef2697edcc821d6fa490586d0c2f4b171b4308c655aa60ba5632664c83.
O recorte CBF15 abaixo deixa de ser pendência de instalação: primeiro exercício2026, residente geral/MZN, activos e condições documentados, colecta integral do projecto ou quota residual documentada, cálculo/gravação/recuperação/relatórios com histórico. As notas QA anteriores ficam como histórico das provas aceites. Outros exercícios/transporte/caducidade, repartição automática/múltiplos projectos, EP/L7/cumulações/outros benefícios/formulários e restantes resíduos L1–L7/L8/L9–L16 permanecem no plano existente. Próximo trabalho: residual executável de CBF15/L8, preservando o recorte instalado e sem repetir as provas aceites. Conferência visual continua pendente no mesmo registo; portal não declarado integral.
## 10/11/2026 — acordo PT–MZ instalado; crédito CBF15 demonstrado em QA
**Homologação: `7c8d4726e9f93325480e2ee6e5e790e9adbcaf19`**, promovida às12:36:07UTC e conferida às12:37:11UTC (14:37 locais). Revisão HTTPS/10hashes iguais, quatro entradasHTTP200; configuração preservada, zero migrations/importações. Backup/restauro190tabelas e369ficheiros/configuração conferidos, b10b35f3 preservada. Recibos `outputs/cdt-agreement-delivery-20261110/`. Falha inicial de recepção Git delimitada ao directório objects/7c pertencente aroot: apenas proprietário/grupo ajustados a conta do serviço,modo0755 inalterado; pedido original validado recolocado sem alterar corpo/SHA ou controlos. Recibos git-permission-repair/request-retry; não foi falha do portal nem alteração de segurança do navegador. Sessão SSH preservada para continuação autenticada, sem credencial em disco.
**Pendência efectivamente encerrada em QA, ainda sem instalação:** CBF15, crédito fiscal por investimento efectivo no primeiro exercício2026, residente geral/ano civil/MZN, colecta inteiramente da actividade do projecto ou quota residual documentada após créditos anteriores e sem outros benefícios próprios/cumulações ou transportes positivos combinados. Reutiliza qualificação CBF de L7 e fonte APIEX já aceite (recibo irpc-l7-common-qa-20261026,compilação oficial2025,PDF66–72,art15PDF71–72), não importação como activação. Localização do investimento,aprovação/condiçõesCBF3/9/10/regime genérico13,início aplicável e activos/documentos exigem correspondência própria. Activo novo corpóreo,nacional,produtivo e fora15(6); condições negativas/exclusões documentadas produzem crédito zero nessa linha,preservando os dados. Informação em falta não é zero.
Percurso completo: preparar detalhe → calcular após económico/internacional e antesPEC/RF/PPC → gravar/actualizar pelo writer privado existente → recuperar → recalcular → HTML/dossier com fornecedor/documento/parcela/valores/qualificação/período/histórico. Maputo cidade5%: investimento10.000 → crédito500 → saldo200 MZN. Restantes províncias10%: crédito1.000 → saldo−300, recuperável simulado devido aoPPC. Cenário de limite: nominal4.000,dedução1.400,crédito não usado2.600,PEC não utilizado200,PPC500; nenhum reembolso de crédito/PEC ou saldo oficial criado/consumido. Provas `outputs/irpc-cbf15-20261110/{city,province,capped,partial}-verification.json` e relatórios respectivos. Quatro versões do mesmo rascunho API em transacção revertida QA55441,com histórico anterior e versões seguintes preservados; zero alterações activas.
Quota residual documentada: nominal4.000, limite do projecto200, dedução200, não utilizado3.800 e saldo500 MZN; recuperada pela API com quatro resultados históricos. Quota superior à colecta global remanescente, negativa ou sem fundamento é recusada. Não há repartição automática de rendimentos/custos/perdas. Tipos, lint/Pint e compilação QA aprovados; provas anteriores reutilizadas.
Livros anteriores L8 permanecem versão1,com chaves/resultados inalterados quando sem CBF15. Só o livro com este novo detalhe usa versão2: cliente anterior recusa o formato,conservando o snapshot,em vez de recalcular ignorando o novo benefício. Não é migration de base. O resumo antigo dos benefícios fica conservado e não reaplicado; ausência dos restantes benefícios exige declaração/reconciliação explícita,mesmo quando CBF15 é ausente. Fonte documental tem capacidade por factura/parcela e entre mapas da entidade; mudar id/data não duplica investimento. Repetir simulação não é uso fiscal anterior.
**Saldo preservado:** CBF15 de outros exercícios/transporte/caducidade,repartição automática/múltiplos projectos,EP/L7/cumulações,outros benefícios e obrigações/formulários oficiais continuam separados. Ausência/zeroCBF15 não bloqueia os saldos anteriores aceites. Restantes L1–L7/L8/L9–L16 e módulos não fiscais mantêm o saldo existente; o portal não está integral. Visual permanece pendente. Conjunto CBF15 preparado para entrega sobre7c8d4726,sem instalação nesta entrada.
## 10/11/2026 — b10b35f3 instalado; acordo institucional PT–MZ em QA
**Homologação: `b10b35f30830922dae00afde50ac2bdb0e8cef6f`**, promovida às12:15:13UTC e conferida às12:16:31UTC (14:16 locais). Revisão HTTPS e16hashes Git iguais, quatro entradasHTTP200; configuração preservada, zero migrations/importações. Backup/restauro190tabelas com contagens/checksums iguais e369ficheiros/configuração conferidos;60b52fc8 disponível para reversão. Recibos `outputs/government-fraction-delivery-20261110/{backup,restore,files-restore,verification}.json`. Sessão SSH mantida aberta para continuação autenticada, sem credencial em disco. Isenção por devedor público11(3)a e fracções económicas L6 deixam de ser pendências de instalação; as anotações anteriores QA são históricas nesses recortes.
**Delta posterior QA, ainda não instalado:** correspondência11(3)b para instituição/organismo beneficiário corrente que concedeu o financiamento no âmbito de acordo concluído entre os governos PT–MZ. Beneficiário, financiamento ligado aos juros e acordo/cláusula aplicável exigem suporte próprio. País, nome da entidade ou qualificação institucional isolada não activam isenção. Fundamento distinto do devedor público11(3)a; limpar/trocar a escolha invalida a decisão corrente, sem apagar resultado histórico nem reutilizar campos ocultos como nova decisão.
Cálculo → gravação/GET pela API existente no QA55441 com rollback → recuperação → HTML/dossier demonstrados. Exemplo sintético: juros brutos60, imposto pago10 e fracção nacional8, com acordo qualificado → limite convencional0/crédito0/saldo708 MZN; sem reembolso, transporte ou consumo oficial e sem alterar L6. Relatório conserva instituição, financiamento e acordo. Prova `outputs/irpc-pt-mz-agreement-20261110/verification.json`; tipos/lint/Pint/build QA aprovados e provas anteriores reutilizadas.
Pacote `outputs/entrega-cdt-acordo-20261110.zip`, base exactab10b35f3, aplicação/reversão conferidas; entrega seguinte expressamente autorizada durante esta continuação, ainda sem instalação nesta entrada. Restantes beneficiários/regimes11(3)b, EP, relações especiais e demais CDT/benefícios/obrigações conservam correspondência própria; saldo L1–L7/restanteL8/L9–L16 preservado. Conferência visual continua pendente no mesmo registo; portal não integral.
## 10/11/2026 — CDT instalada; isenção governamental e precisão económica em QA
**Homologação: `60b52fc8c7446cbbee904a4411eca8ad284aa7ad`**, promovida às11:49:26UTC e conferida às11:50:11UTC (13:50 locais). Delta13hashes Git e revisão HTTPS iguais; portal/login/up/revision HTTP200, .env preservado, zero migrations/importações. Backup fresco/restauro190tabelas com contagens/checksums iguais e369ficheiros/configuração extraídos/conferidos; ac67f9c1 disponível para reversão. Recibos `outputs/cdt-pt-delivery-20261110/{backup,restore,files-restore,verification}.json`. Sessão SSH encerrada, credencial não guardada. Royalties/dividendos/juros ordinários PT–MZ deste recorte deixam de ser pendência de instalação; não reabrir provas aceites.
**Novos fechos dirigidos apenas em QA, ainda não instalados:**
- CDT11(3)a: devedor Estado português, subdivisão política/administrativa ou autarquia, expressamente qualificado e documentado; mantém residência, fonte, definição, período e restantes condições do recorte. Limite convencional0, imposto pago10 sem crédito elegível e saldo708 MZN no exemplo. Não gera reembolso/transporte nem altera L6. Beneficiários/acordos11(3)b e outras situações permanecem separados. Prova `outputs/irpc-pt-mz-government-20261110/verification.json`, com API/gravação/recuperação/HTML/dossier e fundamento do devedor no relatório.
- L6: diferença económica conserva fracções exactas, incluindo subcêntimos e n/d, pelo mecanismo racional existente. Remove a limitação técnica de exigir cêntimos inteiros neste mapa; não cria regra de arredondamento fiscal. Montante do tópico é reconciliado por igualdade racional, evitando tratar0,006 como0 ou aceitar truncamento. Exemplo de IRPC subjacente20,01: nominal12,006, efeito112,006, base4512,006, colecta1443,84192; reconferida L8, saldo723,83592. Repetição/substituição não acumula; valores já considerados/históricos preservados. Prova `outputs/irpc-economic-fraction-20261110/verification.json`, até API/recuperação/HTML/dossier/L8. Outros mapas e arredondamento de declarações oficiais conservam âmbito próprio.
Conjunto novo agrupável sobre60b52fc8; não integra a revisão instalada. Conferência visual permanece pendente no mesmo registo S08. Resíduos L1–L7, restante L8 e L9–L16 preservados; nenhum lote ou portal declarado integral. Próximos ramos: CDT11(3)b e restantes correspondências concretas, obrigações/benefícios e demais percursos conforme saldo existente.
Conjunto posterior preparado em `outputs/entrega-isencao-precisao-20261110.zip`, base exacta60b52fc8. Tipos/lint/Pint/build QA aprovados, aplicação/reversão conferidas em índices temporários. Não instalado; provas e pacotes anteriores preservados.
## 10/11/2026 — económico instalado; CDT Portugal avançada em QA
**Homologação: `ac67f9c13a6561b66c5da788a48849beac90a905`**, promovida pelo executor Git existente às11:32:14UTC e conferida às11:33:16UTC (13:33 locais). Revisão HTTPS e27hashes Git iguais; portal/login/up/revision HTTP200; .env preservado, zero migrations/importações. Backup/restauro190tabelas com contagens/checksums iguais e369ficheiros/configuração extraídos/conferidos. Release c359364d conservada. Recibos `outputs/economic-special-delivery-20261110/{backup,restore,files-restore,verification}.json`. Credencial não guardada; sessão SSH encerrada. Inclui quota económica, inclusão/reconciliação ordinária e especial L6, partilha64(3) e associações64(4). Entradas anteriores «apenas QA» são históricas nestes recortes; não reinstalar por consequência.
**Delta posterior, ainda QA:** correspondência CDT PT–MZ para royalties ordinárias não digitais, dividendos e juros ordinários de fonte portuguesa de residente convencional em Moçambique. País não activa a convenção. Exige beneficiário efectivo, definição/natureza, fonte/pagador, ausência de EP/instalação fixa ligada, período e suporte documentados; relações especiais verificadas nos juros/royalties, isenção governamental11(3) separada. Limite10% do bruto nos ramos suportados; dividendos usam alteração do Protocolo2009 art6, evitando taxa15% do texto1992. Não usa retenção L5 em sentido inverso nem tax sparing presumido. Mantém limite manual/históricos e parcela líquida nacional.
Prova sintética por ramo: bruto60 → limiteCDT6, imposto pago10/fracção nacional8 → crédito6 → saldo702 MZN. Cálculo/gravação/recuperação/HTML/dossier por API existente no QA55441; sem novo writer, schema, direito ou escrita activa. Provas `outputs/irpc-pt-mz-{credit,dividend,interest}-20261110/verification.json`. Fracções mantêm exactidão, sem arredondamento fiscal presumido. País/condição/período inválidos deixam apenas o caso dependente indeterminado. Este delta não integra ac67f9c1.
Restam CDT de outros Estados/naturezas, EP/instalação fixa, juros governamentais, excessos/relações especiais e casos fora do recorte, além dos demais resíduos L1–L7/L8/L9–L16. Conferência visual continua pendente no mesmo registo. Não declarar L8 ou o portal integralmente concluídos.
Conjunto CDT agrupado em `outputs/entrega-cdt-pt-20261110.zip`, base exacta ac67f9c1, aplicação/reversão conferidas; tipos/lint/Pint e build QA aprovados. Instalação deste delta ainda não executada.
## 10/11/2026 — crédito económico: partilha e associações em QA
Homologação mantém `c359364d`. Avanço posterior em QA, sem instalação: art64(3) distingue a parcela elegível do rendimento da partilha e o excesso com natureza de mais-valia; art64(4) distingue associação em participação e à quota, exigindo tributação efectiva documentada na participação. Reutiliza cálculo60%, prova do imposto subjacente, limites, prioridades, isolamento e writer privado existentes. Não calcula a mais-valia como crédito nem pressupõe inclusão L6 ou reconferência da colecta. Campos antigos e históricos são preservados; fontes incompletas ficam recuperáveis e indeterminadas.
Critério demonstrado: calcular → gravar/GET pela API existente no QA55441 com rollback → recuperar exactamente → HTML e relatório do dossier. Partilha sintética: valor atribuído200, entradas100 e rendimento documentado150 → elegível100/excesso50 → quota35,84/crédito12/saldo688 MZN. Associação documentada100/imposto20 → crédito12/saldo688 MZN, sem alterar L6. Provas `outputs/irpc-economic-liquidation-20261110/verification.json` e `outputs/irpc-economic-association-20261110/verification.json`. Não são actos fiscais nem consumo de saldos oficiais.
Conjunto de fontes compatíveis com quota e inclusão ordinária agrupado em `outputs/entrega-economica-especial-20261110.zip`, base c359364d, aplicação/reversão conferidas em índices temporários. Pacotes anteriores intactos. Inclusão/correcção especial em L6 também demonstrada em QA: reconcilia rendimento total da partilha (incluindo mais-valia) uma só vez, enquanto a quota usa apenas a componente elegível; associações reutilizam a qualificação própria. Prova outputs/irpc-economic-special-inclusion-20261110/verification.json: efeito162, base4562/colecta1459,84, quota35,84/saldo739,84 recuperados na API e relatórios. Casos transitórios, fracções de cêntimo, CDT, benefícios/obrigações e restantes resíduos L1–L7/L9–L16 continuam no saldo. Conferência visual pendente. Próximo trabalho funcional: correspondência CDT automática nos casos suportados pelas fontes existentes; eventual instalação deste conjunto é acção separada. Tipos/lint/Pint/build QA conferidos no conjunto final.
## 10/11/2026 — inclusão/correcção económica L6 em QA
Instalado: `c359364d`. Quota económica e inclusão/correcção L6 ordinária demonstradas em QA, sem instalação. Pacote anterior `outputs/entrega-economica-l6-l8-20261110.zip` preservado; novo delta posterior agrupado separadamente. Conferência visual pendente no registo existente.
Escolha explícita da origem L8, reconciliação de lucros/crédito já considerados, prévia da diferença e confirmação precedem o cálculo/gravação existentes. Repetição substitui a parcela; não acumula. Prova: +112 MZN leva lucro tributável a6812, base4512 e colecta1443,84. Montantes já incluídos100/12 dão efeito0; excesso150/20 corrige−58. Origem L8 anterior fica desactualizada; reconferida a nova colecta, quota35,84 e saldo723,84 mantêm-se na API/recuperação/HTML/dossier, com históricos preservados. Provas `outputs/irpc-economic-inclusion-20261110/verification.json` e `outputs/irpc-economic-quota-20261110/verification.json`. Tipos/lint/Pint/build QA aprovados.
Âmbito ordinário residente geral2026/MZN. Fracções de cêntimo exigem tratamento próprio, sem arredondamento presumido. Casos especiais, CDT e obrigações continuam no saldo. Resíduos L1–L7 e L9–L16 preservados; L6/L8 não integrais. Registos anteriores abaixo são históricos.
## Continuação integral — saldo e execução em 10/11/2026
Estado actualizado: conjunto Educação/fracção nacional ordinária instalado e HTTPS verificado em `c359364d`,com backup/restauro190tabelas/369ficheiros,delta14hashes,.env inalterado e zero migrations/importações. Recibos `outputs/education-l8-delivery-20261110/`. As anotações de QA/instalação pendente abaixo ficam históricas neste recorte. Conferência visual permanece separada.
Após essa instalação,avançado o residual de quota económica ordinária após acréscimo53:novo modo deriva de L6 com origem/versionamento e preservação do limite manual,salva/recupera pela API existente e apresenta HTML/dossier. Quatro casos novos+um API revertido/9asserções;tipos/lint/Pint aprovados. Prova `outputs/irpc-economic-quota-20261110/verification.json`. Este novo delta é QA,ainda não instalado. Inclusão/correcção automática do acréscimo na base e casos especiais não incluídos;L8parcial,resíduosL1–L7 eL9–L16 continuam explícitos. Não reabrir a fracção nacional internacional ou Educação para executar esse saldo.
Actualização L8 desta passagem: recorte ordinário de derivação da fracção nacional implementado e demonstrado em QA até gravação/recuperação/relatório pelo writer privado existente. Base4400/colecta1408/parcela100 → fracção32 → saldo simulado676,com limites/provas próprios; caso exacto2/625 e correspondência em falta demonstrados. Nenhuma determinação automática de CDT,repartição de custos/perdas ou consumo oficial. Modo documentado e históricos preservados. Delta agrupável com Educação,ainda sem instalação. Fonte e percurso no início do manual; provas `outputs/irpc-national-fraction-20261110/recovered-reports.json`.
Correcção do saldo global: preservar explicitamente resíduos L1–L7 e L9–L16 (INSS,IRPS mensal/anual,Selo,direitos,ICE,SISA,IPRA),conforme tabela actualizada na visão existente. Transporte/crédito económico entregues em569a1cd7 não são trabalho ausente. Nenhum lote agregado é fechado por número de testes;fecho comunicado nesta passagem é apenas a fracção nacional ordinária em QA.
Entrega agrupada de fontes: `outputs/entrega-educacao-l8-20261110.zip`, base ff726e57, manifesto e patch/reversão conferidos em índices temporários; Educação e L8 sem alterações de schema/rotas/writers. Tipos, lint, Pint e compilação QA finais aprovados. Sem instalação/publicação nova, sem alterar o pacote selado anterior ou o índice de trabalho. Homologação permanece ff726e57.
Instalação `ff726e57` concluída e verificada na homologação: contador/aliases, contactos, pré-dossier, curadoria, sitemap e S08 incluídos. As pendências de instalação dessas entregas nas entradas antigas abaixo são históricas, não trabalho a repetir. Prova: `outputs/reviewed-install-20261110/verification.json`. Conferência visual S08 conserva cinco pontos pendentes no mesmo registo; não bloqueia trabalhos independentes.
Saldo por percursos no início de [visao-consolidada-modulos.md](visao-consolidada-modulos.md), separado em implementação, instalação/conferência e dependência concreta. Matriz existente reconciliada em 35 IDs agregados, conservando 3.555 linhas, estados, critérios originais e todas as células fora das três colunas actualizadas. Não se converteu falta de prova em falta de implementação nem se criou novo inventário.
Primeiro trabalho executado nesta passagem: `AUD-EDU-01/03`, continuidade da revisão de procura/capacidade ao consultar histórico. A paginação normal obrigava a abandonar a preparação para navegar; os dois sentidos passam a conservar o estado e a posição, no mesmo detalhe. Chave por declaração/tipo/actor continua a separar contextos. Quatro verificações novas aprovadas: fundamento conservado, tentativa incerta repetida com a mesma chave, versão nova exige comparação e actor diferente não recebe o fundamento anterior. Tipos globais e lint dos dois ficheiros alterados aprovados. Nenhum teste anterior reexecutado; nenhuma alteração de writer, schema, permissões, dados ou histórico. Delta novo local, sem instalação remota ou prova visual presumida; não confundir com `ff726e57` já instalada.
Compilação QA do delta concluída com código 0; apenas `public/build-qa-opportunities` actualizado para a instalação local existente. Logs de tipos/build: `storage/logs/education-history-continuity-20261110-{types,build}.log`. Build servido de produção, ZIP selado, dados e cluster QA preservados. Não foi feita instalação remota deste delta.
Saldo de Educação: retoma durável da preparação de revisão, jornadas externas/representação sem cadastro aprovado e conferência visual. O delta resolve a perda de continuidade dentro da mesma página, não persistência entre sessões. Próximo trabalho na ordem acordada: continuar a funcionalidade fiscal L8 com fonte executável; preservar IVA/IRPC/PEC e exportações aceites, bloquear apenas os casos cuja regra/prova específica falta. O portal inteiro não fica concluído por este incremento.
## Entrega conjunta preparada em 10/11/2026
[Contador e aliases na mesma entrega](entrega-conjunta-contador-aliases-2026-11-10.md): base `main` e `origin/main` actualizadas e coincidentes em `2209b838`; pacote de fontes com manifesto, patch conjunto e inverso, conferidos num índice temporário. Oito verificações novas de contexto/rotas, 56 asserções; provas aceites do contador e aliases reutilizadas, sem repetir suites anteriores. Sem instalação, push, dados de negócio, configuração ou build servido alterados.
Saldo em três pontos independentes: **disponibilização conjunta pendente; revisão visual pendente; janela S08 como trabalho por concluir, sem conclusão comunicada**. A S08 não é encerrada pela compilação, pelos testes do contador, por uma query transportada ou pela futura revisão visual. Preservar as provas já aceites e os estados/critérios da matriz.
## Continuação de 10/11/2026 — contador preparado; disponibilização e saldo preservados
[Delta multiterritorial e provas](revisao-externa-2026-11-10.md): agregação SQL de IDs únicos de oportunidades públicas e províncias declaradas distintas, sem multiplicar o total pelas localizações. Uma oportunidade em duas províncias conta uma vez no total. Nova chave de cache, invalidadores existentes conservados e definição visível preparados. Nove testes novos/23 asserções passaram, além de PHPStan, Pint, tipos, lint e build em saída privada. Diagnóstico READ ONLY: seis oportunidades/cinco províncias; sem dados de negócio alterados. Cluster QA existente iniciado apenas para tabelas temporárias/rollback e reposto no estado parado; nenhuma base ou migration criada.
**Sem disponibilização no portal**, tanto do contador como dos aliases `/simulador/` e `/contactos/` já corrigidos/testados. **A revisão visual e as melhorias da janela S08 continuam no saldo, sem conclusão comunicada.** Preservar provas aceites e não confundir janela S08 com uma etapa homónima do workflow. A agregação preparada não encerra o P0 inteiro, a S08 ou requisitos territoriais transversais.
## Revisão externa inicial de 10/11/2026
[Reconciliação e primeiro delta P0](revisao-externa-2026-11-10.md): destino confirmado `portal-investidor`; inventário de 3.374 ficheiros e consulta da base configurada apenas em transacção READ ONLY. Aliases `/simulador` e `/contactos` acrescentados para os destinos existentes, com contexto conservado. Nove testes/37 asserções passaram sem base ou envio de email real. Seis IDs da matriz existente receberam somente fontes/evidências, com 3.555 linhas e todos os estados/critérios anteriores preservados.
Saldo P0: agregação multiterritorial e definição visível do contador, conferência de contactos de apoio/identidade já decidida e reprodução visual do cabeçalho em desktop/móvel. Navegador indisponível e HEAD HTTPS impedido pela verificação de revogação Schannel; router directo não substitui essas provas. Nenhuma migration, seed, concessão, parâmetro fiscal, publicação ou deploy. Não recriar simulador, Balcão, biblioteca, contas ou protocolos. Fases estruturais/fiscais seguem a reconciliação específica; nenhuma conclusão global presumida.
<!-- saldo-atual-20261018 -->
O [saldo atualizado em 18/10/2026](visao-consolidada-modulos.md) reconcilia os recibos mais recentes, preserva os fechos aceites e distingue o estado histórico do próximo recorte verificável. As previsões antigas abaixo não devem ser somadas como horas restantes.
<!-- /saldo-atual-20261018 -->
@@ -3489,3 +3854,15 @@ Próximo recorte dos filtros previstos na matriz: selecção por indicador e per
Delta verificado: 12 testes frontend dirigidos aprovados, tipos globais, lint do componente e build QA aprovados. Conferência visual apenas em `127.0.0.2:18644`/`main_visual_20261104_test`, reutilizando as cinco observações sintéticas anteriores, sem novas fixtures ou escrita de dados. Produção em 31/12/2025 devolve uma linha com `125.7500` toneladas; intervalo invertido mostra erro; limpar repõe cinco linhas. As 12 oportunidades do cenário QA mantêm-se. Corrigida e conferida a actualização dos campos de data durante a introdução. Recibo e hashes: `docs/investmoz/unification/quantitative-map-filters-local-20261104.json`; DOM, captura e logs em `outputs/quantitative-filters-20261104/`.
**ACT-02 permanece parcial.** Cobertura factual nacional/regional, séries comparáveis, oferta laboral e metodologia quantitativa de potencial económico continuam pendentes conforme fontes aplicáveis. O cenário sintético não é estatística real. Conferência autenticada de Competitividade pela equipa e requisitos amplos V2-07/V2-10 preservados. Sem push, instalação deste delta, migração, importação, publicação automática, escrita activa, alteração de permissões ou reinício de serviços.
### 10/11/2026 ? commits separados; S08 e visual pendentes
Ajustes locais separados por ?mbito na branch `entrega/directiva-20261110`: pacote contador/aliases `377c40a2`; contactos `4e614802`; informa??o/SEO `4e07557c`; pr?-dossier `dcecd251`; curadoria `7f131497`; sitemap `252f38bb`. Cobertura, limites e hashes em `docs/investmoz/v2/auditoria-portal/ajustes-por-ambito-2026-11-10.md`. Provas aceites preservadas. Sem push ou instala??o.
S08 permanece por concluir, separada da revis?o visual; a especifica??o da janela n?o foi recuperada e n?o ? substitu?da pela etapa de licenciamento hom?nima. Visual interrompido por impossibilidade da ferramenta de confirmar o URL, n?o por Escape nem por falha demonstrada do portal. Cluster QA 55441 mantido ligado enquanto houver depend?ncias por conferir. Nenhum requisito amplo ? promovido a conclu?do por estes commits.
### 10/11/2026 ? S08: melhoria do percurso existente de licenciamento pr?vio
?mbito esclarecido pela Tempo; substitui o anterior saldo ?por identificar?. Implementa??o e sete provas frontend dirigidas em `67344418`: estado sem repeti??o, registo/ac??o principal vis?veis, redu??o dos blocos expans?veis e abertura/foco da etapa por `#S08`, preservando campos/contexto/vers?o/grava??o e guardas existentes. TypeScript, lint dirigido e build QA aprovados. Sem altera??o de backend/hist?rico/dados ou repeti??o das provas aceites n?o afectadas. Detalhes e limites em `docs/investmoz/v2/auditoria-portal/ajustes-por-ambito-2026-11-10.md`. Revis?o visual e instala??o mant?m-se pendentes e separadas. Pacote contador/aliases preservado; sem push e cluster 55441 mantido ligado.
@@ -0,0 +1,43 @@
# Diagnóstico da ligação ao navegador e preparação da entrega
O portal não foi instalado. S08 `67344418`, o registo `659c039b` e o pacote selado de contador/aliases permanecem intactos. A revisão visual continua por executar. Este diagnóstico não atribui uma falha ao portal nem substitui testes visuais por testes de componentes.
## Ligação ao navegador
Diagnósticos executados com os scripts fornecidos pelo plugin Chrome, versão local `26.1103.32239`:
| Componente | Resultado observado |
| --- | --- |
| `chrome-is-running.js --browser chrome --check` | Chrome em execução; exit 0 |
| `installed-browsers.js --json` | Chrome e Edge instalados; navegador predefinido encaminhado pelo Parallels |
| `check-extension-installed.js --browser chrome --json` | Extensão `hehggadaopoacecdllhhajmbjkdcmajg`, versão `1.26.1102.11740_0`, instalada e habilitada no perfil Default; exit 0 |
| `check-native-host-manifest.js --browser chrome --json` | Manifesto, nome, origens permitidas e registo HKCU correctos; exit 0 |
| Processo nativo | `extension-host.exe`, PID 19492, presente; não foi terminado |
| Edge | Extensão habilitada em Profile 2; manifesto correcto. Usa o mesmo host nativo. Não foram alterados perfis ou permissões |
| Alternativa suportada | `mcp__cua_repl.js`: `cua.createBrowserTab('iab','about:blank',{visible:true})` devolveu exactamente `The browser access host is unavailable` |
Na tentativa anterior, o inventário nativo identificou a nova janela Chrome `658216`, título “Tempo - Google Chrome”, mas o inventário de browsers devolveu `Browsers: Error: The browser access host is unavailable`. `cua.getApp({windowId:658216})` encerrou o controlo com `could not determine the current browser URL on Windows with enough confidence to enforce policy`. Nesta ronda foi testado o navegador integrado pelo mecanismo documentado, sem repetir a abertura do Chrome ou provocar novamente o encerramento na confirmação de URL.
**Camada da falha delimitada:** o serviço de acesso ao navegador exposto à sessão está indisponível tanto para descoberta Chrome como para criação IAB, antes de qualquer navegação. A instalação da extensão e o manifesto passam nos diagnósticos. Isso exclui tratá-los como comprovadamente ausentes, mas não demonstra uma comunicação funcional fim a fim. A causa interna da indisponibilidade desse serviço não ficou estabelecida.
Conferidos registos locais do desktop e SQLite de logs, apenas para diagnóstico, sem copiar credenciais ou histórico de navegação para a entrega. Há eventos anteriores `browser_use_iab_backend_startup_ready` e pipes `codex-browser-use-*` existentes. Não foi encontrado um erro específico que explique a indisponibilidade actual. O catálogo local `chrome-native-hosts-v2.json` conserva entradas antigas: a entrada da versão actual indica PID 5564, já inexistente, com última actualização em 07/11; isto é uma anomalia de registo observada, **não uma causa raiz demonstrada**. Não se editaram esses registos nem se ligou manualmente aos pipes.
O transporte nativo configurado é stdio/pipes; não foi identificada uma porta TCP do host em conflito. Conferidas as ligações locais: 55441 pertence ao PostgreSQL PID 20756; 8000 ao servidor PHP PID 14316; 5173 ao Vite PID 17180. Sem ocupação observada em 5180, 9222 ou 9223. Não se activou depuração remota, não se libertaram portas e nenhum desses serviços foi parado. A ausência de um conflito observado não é prova de toda a configuração de rede.
**Intervenção específica para recuperar:** guardar o trabalho e encerrar/reabrir a aplicação desktop ChatGPT/Codex, para recriar o serviço de acesso e a ligação desta sessão; reiniciar só Chrome já foi tentado sem resolver. Essa operação termina a sessão de controlo e deve ser feita pelo utilizador. Depois, a primeira verificação é `cua.listBrowsers()` e uma abertura IAB, antes de tocar em S08. Se o serviço continuar indisponível, reinstalar o plugin Browser/Chrome pela interface oficial de Plugins e repetir o diagnóstico, ou fornecer estes erros ao suporte da aplicação. Não reinstalar o portal ou desligar QA.
O guia local [Chrome troubleshooting](C:/Users/macbook/.codex/plugins/cache/openai-bundled/chrome/latest/docs/chrome-troubleshooting.md) determina: “Do not install or repair the native host yourself.” Não foi executada reparação manual do manifesto/host. A [documentação oficial de configuração](https://developers.openai.com/training/walkthroughs/using-your-computer-and-browser) descreve a instalação dos plugins Browser e Chrome e da extensão; não estabelece a causa interna deste erro específico.
## Revisão do código e compatibilidade
`git fetch --no-tags origin main` executado com sucesso. `main` e `origin/main` continuam em `2209b8382019b0bbc2be57b3f6e665a3c94dd032`, a base preparada. Nenhum push ou alteração de main.
Revistos os deltas de S08, formulário/endpoint/contexto e condicionais de autoridade; curadoria com autorização nativa/MFA, prévia vinculada ao actor/versão/intent, transacção e recuperação; sitemap limitado ao scope público/origem configurada; pré-dossier sobre a projecção autorizada comum. Esta revisão não é nova homologação integral nem ensaio de carga.
Identificado um caso adicional em S08: uma resposta de gravação que muda `path.next.code` pode fechar a fase controlada, apesar de o fragmento continuar em `#S08`. Correcção adicional em `03bcaf38`: o efeito de abertura também acompanha essa mudança, sem alterar campos ou contrato de gravação. Commit original preservado. Executado apenas o teste afectado com `node --test --test-name-pattern='server next-stage updates' tests/frontend/licensing-stage.test.mjs`: 1/1 aprovado; TypeScript global aprovado. As outras seis provas aceites e as suites do servidor não foram repetidas.
## Entrega preparada e saldo
Entrega incremental de fontes em `outputs/entrega-revisada-20261110/`, com manifesto, patches cumulativos/incrementais, série de commits e reversões. A conferência em índices Git temporários valida aplicação e reversão contra a base de main e contra a árvore selada do contador/aliases, sem tocar no índice de trabalho ou instalar ficheiros. Os hashes finais constam do manifesto e do recibo de compatibilidade do pacote. Builds QA anteriores permanecem provas locais, não artefactos para produção; o delta adicional tem prova dirigida e TypeScript, sem novo build instalado.
Continuam separados: revisão visual real de S08 e dos percursos afectados; instalação/activação; confirmação operacional autenticada no destino. O pacote não autoriza publicação automática nem concessões ou operações de negócio. Cluster QA 55441 ligado; servidor e workers existentes preservados.
@@ -0,0 +1,50 @@
# Entrega conjunta do contador e dos aliases
## Resultado
Pacote de fontes preparado em 10/11/2026 para reunir o contador multiterritorial e os aliases `/simulador/` e `/contactos/`. Sem instalação, publicação, push ou alteração de dados/configurações. O pacote contém patch conjunto, patch inverso, fontes seleccionadas, manifesto de hashes, recibos e procedimento de aplicação/reversão futura.
Directório local: `outputs/entrega-conjunta-contador-aliases-20261110/`; arquivo com o mesmo nome e extensão `.zip`. O manifesto e `compatibilidade.json` identificam exactamente a base, a árvore candidata e os resultados da aplicação/reversão. É uma entrega de fontes; o build QA anterior é evidência e não um build de produção a instalar.
## Compatibilidade com main
A referência remota foi actualizada por `git fetch --no-tags origin main`. `main`, `origin/main` e `FETCH_HEAD` coincidem em `2209b8382019b0bbc2be57b3f6e665a3c94dd032`. A preparação não altera branches nem cria um commit em `main`.
O patch é verificado num índice Git temporário iniciado nessa base: `git apply --cached --check`, aplicação ao índice e comparação da árvore resultante com a candidata. O patch inverso é conferido e aplicado no mesmo índice; a árvore final deve ser exactamente a árvore da base. O índice de trabalho original é preservado por hash. Nenhuma destas operações instala os ficheiros na árvore de trabalho ou no portal.
Esta prova cobre a base exacta identificada. Se `main` avançar, é necessário voltar a conferir o delta afectado; não se declara compatibilidade com commits futuros. A lista de ficheiros é fechada e não inclui `.env`, credenciais, dumps, dependências, builds servidos ou alterações do SGF Tempo.
## Ajustes incluídos
- Total de oportunidades por ID público único; várias localizações não multiplicam a oportunidade. Províncias declaradas distintas a partir da referência principal e das adicionais, pelas regras públicas existentes.
- Chave de cache versionada, invalidação nos dois idiomas e definição do indicador junto da contagem. Sem limpeza da cache activa ou alteração da validade de dez minutos.
- Aliases minúsculos, com e sem barra final, para as rotas existentes `/simulador-fiscal` e `/apoio`, por 301. O alias histórico `/Simulador` e os destinos oficiais são preservados.
- Testes e registos necessários à entrega conjunta, incluindo a matriz e o manual existentes. Sem migrations, novas tabelas de negócio, direitos ou parâmetros fiscais.
## Contexto nos redireccionamentos
Oito casos novos passaram, com 56 asserções: GET e HEAD nos dois aliases, com e sem barra final. Os testes conferem 301, esquema e host, caminho canónico, nome da rota existente e equivalência dos parâmetros após codificação/descodificação.
O contexto inclui dossier, oportunidade, idioma, origem/território aninhados, retorno com query própria e texto com acentos e caracteres reservados. Um parâmetro `redirect` com URL externa permanece dado de consulta; não substitui o destino. O valor de etapa transportado dentro de um retorno é opaco: conservá-lo não demonstra a funcionalidade dessa etapa ou da janela S08.
Os testes executam a colecção de rotas e a resposta, com acesso à base proibido. Não são prova de navegador, middleware completo, nova autenticação, retomada de dossier ou apresentação visual do destino. Fragmentos do browser não são parâmetros enviados ao servidor e não foram ensaiados.
## Provas anteriores reutilizadas
O recibo `home-territorial-delivery.json` continua a corresponder, por SHA-256, aos sete ficheiros de fontes/testes do contador e ao manifesto do build privado anterior. Reutilizam-se os nove testes/23 asserções e as verificações de tipos, lint, PHPStan, Pint e compilação nesse âmbito. Não se repetiram a suite SQL, os testes aceites dos aliases, os ciclos do simulador/Balcão ou provas da S08. O teste novo passou Pint; `git diff --check` não detectou erros de whitespace no delta.
As provas históricas e os limites da leitura READ ONLY seis oportunidades/cinco províncias estão no [relatório anterior](revisao-externa-2026-11-10.md). Não há novo ensaio de dados de negócio, arranque de QA ou consulta à base nesta preparação.
## Saldo preservado
1. **Instalação/disponibilização conjunta:** pendente. O pacote preparado não torna contador ou aliases disponíveis no portal.
2. **Revisão visual:** pendente, com âmbito próprio de desktop/móvel e percursos afectados. Aplicação limpa do patch, testes de router e build não a encerram.
3. **Janela S08:** trabalho por concluir, sem conclusão comunicada. É uma pendência separada da revisão visual e não foi resolvida pelos testes do contador ou pelo transporte de contexto. As provas já aceites mantêm-se; não se atribui conteúdo à janela a partir de uma etapa homónima.
## Procedimento de entrega e reversão
Antes de uma futura aplicação, conferir a base e os hashes do manifesto, preservar alterações locais e efectuar a verificação do patch no destino correcto. A instalação exige o procedimento já aplicável ao portal e a autorização correspondente; não executar estes passos sobre dados activos por inferência desta preparação.
O pacote fornece um delta único para evitar instalar o contador sem os invalidadores/definição, ou distribuir os aliases sem o seu contexto e testes. O frontend exige o build do modo e ambiente de destino: não copiar o build QA com identificação de QA para produção. Não há migration ou comando SQL de instalação.
A reversão é delimitada aos ficheiros desse delta sobre a árvore candidata exacta. Conferir o patch inverso antes de o usar; não aplicar sobre edições posteriores nem repor o repositório inteiro. Nenhum rollback de base, limpeza de cache global, eliminação de dados, reset destrutivo ou reinstalação de dependências faz parte desta entrega.