.
This commit is contained in:
@@ -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']);
|
||||
}
|
||||
}
|
||||
@@ -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> */
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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')"));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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)]);
|
||||
|
||||
@@ -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'] !== []) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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
@@ -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
@@ -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.
|
||||
Reference in New Issue
Block a user