diff --git a/app/Console/Commands/ExpirePrivateOffers.php b/app/Console/Commands/ExpirePrivateOffers.php new file mode 100644 index 00000000..562383f2 --- /dev/null +++ b/app/Console/Commands/ExpirePrivateOffers.php @@ -0,0 +1,37 @@ +whereNotNull('workflow_context->private_offer_preparation')->orderBy('id')->chunkById(100, function ($records) use (&$count) { + foreach ($records as $record) { + if (app(OfferAvailability::class)->record($record)['state'] !== 'expired') { + continue; + } + if ($this->option('confirm')) { + if (app(PrivateOfferOperations::class)->expire($record->id)) { + $count++; + } + } else { + $count++; + } + } + }); + $this->info(($this->option('confirm') ? 'Expirações registadas: ' : 'Expiradas, sem escrita: ').$count); + + return self::SUCCESS; + } +} diff --git a/app/Http/Controllers/OfferPublicCatalogueController.php b/app/Http/Controllers/OfferPublicCatalogueController.php index 01d4ef9b..008c77c8 100644 --- a/app/Http/Controllers/OfferPublicCatalogueController.php +++ b/app/Http/Controllers/OfferPublicCatalogueController.php @@ -8,7 +8,7 @@ use App\Services\OpportunityManagement; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Inertia\Inertia; -use Inertia\Response; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\StreamedResponse; final class OfferPublicCatalogueController extends Controller @@ -17,30 +17,30 @@ final class OfferPublicCatalogueController extends Controller { $filters = $catalogue->filters($request); - return Inertia::render('offers/catalogue', [...$catalogue->page($request, $filters), 'filters' => $filters, + return $this->uncached(Inertia::render('offers/catalogue', [...$catalogue->page($request, $filters), 'filters' => $filters, 'mapData' => in_array($filters['view'] ?? '', ['map', 'combined'], true) ? $catalogue->map($filters) : null, 'territorySelection' => app(InvestmentGeography::class)->selection($filters), - 'references' => ['categories' => OpportunityManagement::CATEGORIES, 'options' => [], 'clusters' => [], 'corridors' => [], 'projects' => [], 'locations' => [], 'sectors' => []]]); + 'references' => ['categories' => OpportunityManagement::CATEGORIES, 'options' => [], 'clusters' => [], 'corridors' => [], 'projects' => [], 'locations' => [], 'sectors' => []]])->toResponse($request)); } - public function show(int $id, OfferPublicCatalogue $catalogue): Response + public function show(Request $request, int $id, OfferPublicCatalogue $catalogue): Response { - return Inertia::render('offers/public-detail', ['offer' => $catalogue->detail($id)]); + return $this->uncached(Inertia::render('offers/public-detail', ['offer' => $catalogue->detail($id)])->toResponse($request)); } public function search(Request $request, OfferPublicCatalogue $catalogue): JsonResponse { - return response()->json($catalogue->page($request, $catalogue->filters($request))); + return response()->json($catalogue->page($request, $catalogue->filters($request)))->header('Cache-Control', 'private, no-store'); } public function detail(int $id, OfferPublicCatalogue $catalogue): JsonResponse { - return response()->json(['data' => $catalogue->detail($id)]); + return response()->json(['data' => $catalogue->detail($id)])->header('Cache-Control', 'private, no-store'); } public function map(Request $request, OfferPublicCatalogue $catalogue): JsonResponse { - return response()->json($catalogue->map($catalogue->filters($request))); + return response()->json($catalogue->map($catalogue->filters($request)))->header('Cache-Control', 'private, no-store'); } public function export(Request $request, OfferPublicCatalogue $catalogue): StreamedResponse @@ -60,6 +60,13 @@ final class OfferPublicCatalogueController extends Controller fputcsv($stream, array_map(fn ($value) => preg_match('/^[\s]*[=+@-]/u', (string) $value) ? "'".$value : $value, $fields), ';', '"', ''); } fclose($stream); - }, 'ofertas.csv', ['Content-Type' => 'text/csv; charset=UTF-8', 'X-Export-Limit' => '1000', 'X-Results-Truncated' => $result['truncated'] ? 'true' : 'false']); + }, 'ofertas.csv', ['Content-Type' => 'text/csv; charset=UTF-8', 'Cache-Control' => 'private, no-store', 'X-Export-Limit' => '1000', 'X-Results-Truncated' => $result['truncated'] ? 'true' : 'false']); + } + + private function uncached(Response $response): Response + { + $response->headers->set('Cache-Control', 'private, no-store'); + + return $response; } } diff --git a/app/Http/Controllers/PortalEditorialWorkflowController.php b/app/Http/Controllers/PortalEditorialWorkflowController.php index 6dc5dcd1..656f28ea 100644 --- a/app/Http/Controllers/PortalEditorialWorkflowController.php +++ b/app/Http/Controllers/PortalEditorialWorkflowController.php @@ -107,6 +107,7 @@ final class PortalEditorialWorkflowController extends Controller $destinations = app(ContentManagement::class)->access($actor); $data = ['items' => $items, 'filters' => $filters, 'notices' => $workflow->notices($actor), 'resourceAccess' => $access->full($actor) || $access->editorial($actor), 'opportunitiesUrl' => $destinations['opportunities']['index'] ?? null, 'offersUrl' => isset($destinations['opportunities']['index']) && app(PortalProfileAccess::class)->available() ? '/minhas-oportunidades/ofertas' : null, + 'offerOperationsUrl' => ! app(PortalProfileAccess::class)->has($actor, 'auditor') && (app(PortalProfileAccess::class)->has($actor, 'validator') || app(PortalProfileAccess::class)->has($actor, 'publisher')) ? '/minhas-oportunidades/ofertas?operational=1' : null, 'opportunityPending' => $workflow->opportunityPending($actor), 'clustersUrl' => $destinations['clusters']['index'] ?? null, 'portfolioUrl' => $destinations['portfolio']['index'] ?? null, 'portfolioPending' => $workflow->portfolioPending($actor), 'demographyUrl' => $destinations['demography']['index'] ?? null, 'demographyPending' => $workflow->demographyPending($actor), 'territorialUrl' => app(TerritorialRegistry::class)->editorialAvailable() && $actor->can('manage-portal') ? '/management/territorial/editorial' : null, diff --git a/app/Http/Controllers/PrivateOfferOperationsController.php b/app/Http/Controllers/PrivateOfferOperationsController.php new file mode 100644 index 00000000..80a08c72 --- /dev/null +++ b/app/Http/Controllers/PrivateOfferOperationsController.php @@ -0,0 +1,51 @@ +json($operations->presentation($request->user(), $id)); + } + + public function owner(Request $request, int $id, PrivateOfferOperations $operations): Response + { + $operations->ownerAction($request->user(), $id, $request->all()); + + return response()->json($operations->presentation($request->user(), $id)); + } + + public function reportForm(Request $request, int $id, PrivateOfferOperations $operations): Response + { + $record = Opportunity::findOrFail($id); + abort_unless($record->isPrivateOfferPreparation() && $operations->canReport($request->user(), $record), 404); + + return Inertia::render('offers/report', ['offerId' => $id])->toResponse($request); + } + + public function report(Request $request, int $id, PrivateOfferOperations $operations): Response + { + return response()->json($operations->report($request->user(), $id, $request->all()), 201); + } + + public function handleReport(Request $request, int $id, string $reportId, PrivateOfferOperations $operations): Response + { + $operations->handleReport($request->user(), $id, $reportId, $request->all()); + + return response()->json($operations->presentation($request->user(), $id)); + } + + public function highlight(Request $request, int $id, PrivateOfferOperations $operations): Response + { + $operations->highlight($request->user(), $id, $request->all()); + + return response()->json($operations->presentation($request->user(), $id)); + } +} diff --git a/app/Http/Controllers/PrivateOfferPreparationController.php b/app/Http/Controllers/PrivateOfferPreparationController.php index 3e010977..c76dbd33 100644 --- a/app/Http/Controllers/PrivateOfferPreparationController.php +++ b/app/Http/Controllers/PrivateOfferPreparationController.php @@ -3,12 +3,14 @@ namespace App\Http\Controllers; use App\Models\Opportunity; +use App\Services\OfferAvailability; use App\Services\OpportunityCanonicalContract; use App\Services\OpportunityManagement; use App\Services\OpportunityProfileWorkflow; use App\Services\OpportunityResources; use App\Services\PortalProfileAccess; use App\Services\PrivateOfferConfidentiality; +use App\Services\PrivateOfferOperations; use App\Services\PrivateOfferPreparation; use App\Services\VerifiedDocumentBinary; use Illuminate\Http\JsonResponse; @@ -86,14 +88,20 @@ final class PrivateOfferPreparationController extends Controller public function index(Request $request, PortalProfileAccess $access): Response { - $filters = $request->validate(['state' => 'nullable|in:draft,submitted,in_review,needs_information,rejected,approved']); - $records = $access->scope(Opportunity::query(), $request->user())->whereNotNull('workflow_context->private_offer_preparation') + $filters = $request->validate(['state' => 'nullable|in:draft,submitted,in_review,needs_information,rejected,approved', 'operational' => 'nullable|boolean', 'pending_reports' => 'nullable|boolean']); + $query = ($filters['operational'] ?? false) ? app(PrivateOfferOperations::class)->staffQuery($request->user()) : $access->scope(Opportunity::query(), $request->user()); + if ($filters['pending_reports'] ?? false) { + $query = app(PrivateOfferOperations::class)->staffQuery($request->user()); + $query->whereRaw("jsonb_path_exists(COALESCE(workflow_context->'offer_reports','{}'::jsonb), '$.* ? (@.status == \"open\" || @.status == \"in_review\")')"); + } + $records = $query->whereNotNull('workflow_context->private_offer_preparation') ->when($filters['state'] ?? null, fn ($q, $state) => $q->where('workflow_context->private_offer_preparation->state', $state)) ->orderByDesc('id')->paginate(20)->withQueryString(); return Inertia::render('offers/preparation-index', ['records' => $records->through(fn ($r) => [ 'id' => $r->id, 'title' => app(OpportunityManagement::class)->values($r)['title'] ?? null, 'reference' => $r->reference, 'state' => $access->state($r), + 'availability' => app(OfferAvailability::class)->record($r)['state'], ]), 'filters' => $filters, 'canCreate' => count($access->navigation($request->user())['contexts']) > 0]); } diff --git a/app/Services/OfferAvailability.php b/app/Services/OfferAvailability.php new file mode 100644 index 00000000..2f1eb073 --- /dev/null +++ b/app/Services/OfferAvailability.php @@ -0,0 +1,80 @@ + 'unavailable', 'expires_at' => null, 'policy_pending' => true]; + } + $policy = is_array($operation) ? ($operation['validity_policies'][$key] ?? $operation['validity'] ?? null) : null; + $pending = ! is_array($policy) || ($policy['days'] ?? null) === null; + $result = ['state' => 'available', 'expires_at' => null, 'policy_pending' => $pending]; + if ($operation !== null && ! is_array($operation)) { + return [...$result, 'state' => 'unavailable']; + } + $state = $operation['status'] ?? 'available'; + if (! is_int($operation['cycle'] ?? 0) || ($operation['cycle'] ?? 0) < 0) { + return [...$result, 'state' => 'unavailable']; + } + if (! in_array($state, ['available', 'withdrawn', 'expired', 'revalidation'], true)) { + return [...$result, 'state' => 'unavailable']; + } + if ($state !== 'available') { + return [...$result, 'state' => $state]; + } + $days = $policy['days'] ?? null; + if (($operation['cycle'] ?? 0) > 0 && ($publication['offer_cycle'] ?? null) !== $operation['cycle']) { + return $result; + } + if ($days === null || ! is_array($publication) || ($publication['publication']['state'] ?? null) !== 'published') { + return $result; + } + if (! is_int($days) || $days < 1 || $days > 36500) { + return [...$result, 'state' => 'unavailable']; + } + try { + $at = $publication['publication']['at'] ?? null; + if (! is_string($at) || ! preg_match('/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,6})?(?:Z|[+-]\d{2}:\d{2})$/D', $at)) { + return [...$result, 'state' => 'unavailable']; + } + $start = CarbonImmutable::parse($at); + $end = $start->addDays($days)->utc(); + } catch (\Throwable) { + return [...$result, 'state' => 'unavailable']; + } + + return [...$result, 'expires_at' => $end->toIso8601String(), 'state' => $end->lte(CarbonImmutable::now()) ? 'expired' : 'available']; + } + + /** @return array{state:string,expires_at:?string,policy_pending:bool} */ + public function record(Opportunity $record): array + { + return $this->view($record->workflow_context['offer_operation'] ?? null, $record->workflow_context['offer_publication'] ?? null); + } + + public function publicAllowed(mixed $operation, mixed $publication): bool + { + if ($this->view($operation, $publication)['state'] !== 'available') { + return false; + } + $cycle = $operation['cycle'] ?? 0; + if (! is_int($cycle) || $cycle < 0) { + return false; + } + + // A new validation must never resurrect the previous public snapshot. + return $cycle === 0 || (is_array($publication) && ($publication['offer_cycle'] ?? null) === $cycle + && is_string($operation['activated_hash'] ?? null) && ($publication['editorial_hash'] ?? null) === $operation['activated_hash']); + } +} diff --git a/app/Services/OfferPublicCatalogue.php b/app/Services/OfferPublicCatalogue.php index 85a1b18d..867c5f4b 100644 --- a/app/Services/OfferPublicCatalogue.php +++ b/app/Services/OfferPublicCatalogue.php @@ -120,7 +120,7 @@ final class OfferPublicCatalogue return ['records' => [], 'truncated' => false]; } $rows = Opportunity::query()->whereNotNull('workflow_context->private_offer_preparation')->whereNotNull('workflow_context->offer_publication') - ->orderBy('id')->limit(1001)->get(['id', DB::raw("workflow_context->'offer_publication' AS public_snapshot")]); + ->orderBy('id')->limit(1001)->get(['id', DB::raw("workflow_context->'offer_publication' AS public_snapshot"), DB::raw("workflow_context->'offer_operation' AS operation_state")]); $records = []; foreach ($rows->take(1000) as $row) { if (! is_string($row->getAttribute('public_snapshot'))) { @@ -128,13 +128,21 @@ final class OfferPublicCatalogue } try { $publication = json_decode($row->getAttribute('public_snapshot'), true, 512, JSON_THROW_ON_ERROR); + $operation = json_decode($row->getAttribute('operation_state') ?? 'null', true, 512, JSON_THROW_ON_ERROR); } catch (\JsonException $error) { continue; } + if (! app(OfferAvailability::class)->publicAllowed($operation, $publication)) { + continue; + } $p = $this->project($row->id, $publication); if (! $p) { continue; } + $expires = app(OfferAvailability::class)->view($operation, $publication)['expires_at']; + if ($expires !== null) { + $p['expires_at'] = $expires; + } if (! empty($filters['q']) && ! str_contains(mb_strtolower($p['title'].' '.$p['summary']), mb_strtolower($filters['q']))) { continue; } @@ -184,17 +192,23 @@ final class OfferPublicCatalogue public function detail(int $id): array { abort_unless($this->fixtureReadAllowed(), 404); - $row = Opportunity::query()->whereKey($id)->whereNotNull('workflow_context->private_offer_preparation')->first(['id', DB::raw("workflow_context->'offer_publication' AS public_snapshot")]); + $row = Opportunity::query()->whereKey($id)->whereNotNull('workflow_context->private_offer_preparation')->first(['id', DB::raw("workflow_context->'offer_publication' AS public_snapshot"), DB::raw("workflow_context->'offer_operation' AS operation_state")]); if (! $row || ! is_string($row->getAttribute('public_snapshot'))) { abort(404); } try { $publication = json_decode($row->getAttribute('public_snapshot'), true, 512, JSON_THROW_ON_ERROR); + $operation = json_decode($row->getAttribute('operation_state') ?? 'null', true, 512, JSON_THROW_ON_ERROR); } catch (\JsonException $error) { abort(404); } + abort_unless(app(OfferAvailability::class)->publicAllowed($operation, $publication), 404); $record = $this->project($id, $publication); abort_unless($record !== null, 404); + $expires = app(OfferAvailability::class)->view($operation, $publication)['expires_at']; + if ($expires !== null) { + $record['expires_at'] = $expires; + } return $record; } diff --git a/app/Services/OpportunityProfileWorkflow.php b/app/Services/OpportunityProfileWorkflow.php index cc1b9d9b..deddf692 100644 --- a/app/Services/OpportunityProfileWorkflow.php +++ b/app/Services/OpportunityProfileWorkflow.php @@ -116,7 +116,9 @@ class OpportunityProfileWorkflow if (in_array($state, ['draft', 'needs_information'], true)) { $actions[] = 'submit'; } - if (in_array($state, ['rejected', 'approved'], true)) { + if (in_array($state, ['rejected', 'approved'], true) || (isset($record->workflow_context['submission']) && ($record->workflow_context['offer_operation']['status'] ?? null) === 'revalidation' + && ($record->workflow_context['offer_operation']['requested_by'] ?? null) === $actor->id && (int) $record->owner_user_id === $actor->id + && in_array($state, ['draft', 'needs_information', 'submitted', 'in_review', 'information_pending'], true))) { $actions[] = 'request_revision'; } } @@ -232,7 +234,8 @@ class OpportunityProfileWorkflow 'review_gate' => ['submitted', 'in_review', 'information_pending', 'approved'], 'publish' => ['approved'], 'suspend' => [$state], 'assign_publication' => ['approved'], - 'request_revision' => $offer ? ['approved', 'rejected'] : ['approved', 'published', 'suspended'], + 'request_revision' => $offer ? (($context['offer_operation']['status'] ?? null) === 'revalidation' + ? ['draft', 'needs_information', 'submitted', 'in_review', 'information_pending', 'approved', 'rejected'] : ['approved', 'rejected']) : ['approved', 'published', 'suspended'], 'archive' => ['draft', 'submitted', 'in_review', 'information_pending', 'needs_information', 'approved', 'rejected', 'published', 'suspended'], 'request_archive' => ['draft', 'submitted', 'in_review', 'information_pending', 'needs_information', 'approved', 'published', 'suspended'], 'appeal', 'appeal_uphold', 'appeal_reopen' => ['rejected'], @@ -378,6 +381,9 @@ class OpportunityProfileWorkflow && $this->responsibility($reviewer, $record, preg_match('/legal|registry|authority|confidentiality|license/', $gate) === 1 ? 'legal_reviewer' : 'technical_reviewer'), 403, 'Revisor revogado, sem responsabilidade ou com conflito.'); } $context['approval'] = [...$decision, 'hash' => $snapshot['hash'], 'gates' => $context['reviews'] ?? []]; + if ($offer) { + $context['offer_operation'] = app(PrivateOfferOperations::class)->afterApproval($context); + } $this->tasks->complete($context['review_task_id'] ?? null, $data['reason']); $record->workflow_context = $context; if (! $offer) { diff --git a/app/Services/PrivateOfferConfidentiality.php b/app/Services/PrivateOfferConfidentiality.php index a20f02cf..8f795ade 100644 --- a/app/Services/PrivateOfferConfidentiality.php +++ b/app/Services/PrivateOfferConfidentiality.php @@ -61,6 +61,7 @@ final class PrivateOfferConfidentiality private function approvedHash(Opportunity $record): string { + abort_unless(app(OfferAvailability::class)->record($record)['state'] === 'available', 409, 'A oferta não está disponível.'); $hash = $record->workflow_context['approval']['hash'] ?? null; abort_unless(is_string($hash) && app(PortalProfileAccess::class)->state($record) === 'approved', 409, 'Esta versão não tem aprovação editorial actual.'); try { diff --git a/app/Services/PrivateOfferOperations.php b/app/Services/PrivateOfferOperations.php new file mode 100644 index 00000000..19bdbbe3 --- /dev/null +++ b/app/Services/PrivateOfferOperations.php @@ -0,0 +1,296 @@ + $context + * @return array */ + public function afterApproval(array $context): array + { + $days = config('private_offers.validity_days'); + $reference = config('private_offers.validity_policy_reference'); + if ($days !== null && (! is_int($days) || $days < 1 || $days > 36500 || ! is_string($reference) || trim($reference) === '')) { + throw ValidationException::withMessages(['validity' => 'Configuração de validade incompleta. Não foi aplicado um prazo presumido.']); + } + $operation = $context['offer_operation'] ?? ['status' => 'available', 'cycle' => 0]; + $hash = $context['approval']['hash']; + $policy = ['days' => $days, 'policy_reference' => $reference, 'approval_hash' => $hash]; + $operation['validity_policies'][$hash] ??= $policy; + $operation['validity'] ??= $operation['validity_policies'][$hash]; + $operation['last_approval_hash'] = $hash; + + return $operation; + } + + private function offer(int $id, bool $lock = false): Opportunity + { + $record = Opportunity::query()->when($lock, fn ($q) => $q->lockForUpdate())->findOrFail($id); + abort_unless($record->isPrivateOfferPreparation(), 404); + + return $record; + } + + public function publisher(User $actor, Opportunity $record): bool + { + $access = app(PortalProfileAccess::class); + + return $access->canView($actor, $record) && $access->mfa($actor) && ! $access->has($actor, 'auditor') && ! $access->conflict($actor, $record) + && $access->scoped($actor, 'publisher', $record->organization_id, $record->opportunity_category) && $access->operational($actor, 'publish', $record->id); + } + + /** @return Builder */ + public function staffQuery(User $actor): Builder + { + $access = app(PortalProfileAccess::class); + abort_unless($access->mfa($actor) && ! $access->has($actor, 'auditor'), 403); + $grants = [...$access->grants($actor, 'validator'), ...$access->grants($actor, 'publisher')]; + abort_unless($grants !== [], 403); + + return $access->scope(Opportunity::query(), $actor)->whereNotNull('workflow_context->private_offer_preparation') + ->where('owner_user_id', '!=', $actor->id)->whereRaw("NOT COALESCE(workflow_context->'contributors','[]'::jsonb) @> ?::jsonb", [json_encode([$actor->id])]) + ->where(fn ($q) => $q->whereNull('organization_id')->orWhereNotIn('organization_id', DB::table('memberships')->where('user_id', $actor->id)->whereNull('deleted_at')->select('organization_id'))) + ->where(function ($q) use ($grants) { + foreach ($grants as $grant) { + $q->orWhere(function ($scope) use ($grant) { + $scope->whereIn('opportunity_category', json_decode($grant->opportunity_types, true, 512, JSON_THROW_ON_ERROR)); + if ($grant->organization_id !== null) { + $scope->where('organization_id', $grant->organization_id); + } + }); + } + }); + } + + /** @return array */ + public function presentation(User $actor, int $id): array + { + $record = app(PrivateOfferPreparation::class)->record($actor, $id); + $reviewer = app(PrivateOfferConfidentiality::class)->internal($actor, $record); + $publisher = $this->publisher($actor, $record); + $reports = []; + if ($reviewer) { + foreach ($record->workflow_context['offer_reports'] ?? [] as $report) { + $reports[] = [...$report, 'reason' => Crypt::decryptString($report['reason']), 'resolution_reason' => isset($report['resolution_reason']) ? Crypt::decryptString($report['resolution_reason']) : null, + 'source_snapshot' => isset($report['source_snapshot']) ? json_decode(Crypt::decryptString($report['source_snapshot']), true, 512, JSON_THROW_ON_ERROR) : null]; + } + } + $highlight = $record->workflow_context['offer_highlight'] ?? null; + + return ['id' => $id, 'etag' => app(OpportunityManagement::class)->etag($record), + 'availability' => app(OfferAvailability::class)->record($record), 'cycle' => $record->workflow_context['offer_operation']['cycle'] ?? 0, + 'owner' => app(PrivateOfferConfidentiality::class)->seller($actor, $record), 'reviewer' => $reviewer, 'publisher' => $publisher, + 'editorial_state' => app(PortalProfileAccess::class)->state($record), 'reports' => $reports, + 'highlight' => $publisher && $highlight ? [...$highlight, 'note' => Crypt::decryptString($highlight['note'])] : null, + 'commercial_activation' => false, + 'history' => array_map(fn ($event) => array_intersect_key($event, array_flip(['id', 'action', 'actor_id', 'at', 'cycle'])), $record->workflow_context['offer_operation_history'] ?? [])]; + } + + /** @param array $context + * @param array $metadata */ + private function save(Opportunity $record, ?User $actor, array $context, string $action, array $metadata = []): void + { + $event = ['id' => (string) Str::uuid(), 'action' => $action, 'actor_id' => $actor?->id, 'at' => now()->toIso8601String(), + 'cycle' => $context['offer_operation']['cycle'] ?? 0, ...$metadata]; + $context['offer_operation_history'][] = $event; + $record->workflow_context = $context; + $record->management_version++; + $record->save(); + DB::table('audit_events')->insert(['source_system' => 'private-offers', 'entity_type' => 'opportunity', 'actor_user_id' => $actor?->id, + 'action' => $action, 'data_classification' => 'restricted', 'correlation_id' => (string) Str::uuid(), + 'after_data' => json_encode(['record_id' => $record->id, 'record_version' => $record->management_version, ...$event], JSON_THROW_ON_ERROR)]); + } + + /** @param array $context + * @return array */ + private function withdraw(array $context, string $origin): array + { + $context['offer_operation'] = [...($context['offer_operation'] ?? ['cycle' => 0]), 'status' => 'withdrawn', 'withdrawn_at' => now()->toIso8601String(), 'origin' => $origin]; + + return $context; + } + + /** @param array $input */ + public function ownerAction(User $actor, int $id, array $input): Opportunity + { + $data = validator($input, ['action' => 'required|in:withdraw,request_reactivation,reactivate', 'confirm' => 'required|accepted', + 'etag' => 'required_unless:action,withdraw|string|size:64', 'reason' => 'nullable|string|max:1000'])->validate(); + + return DB::transaction(function () use ($actor, $id, $data) { + $record = $this->offer($id, true); + abort_unless(app(PrivateOfferConfidentiality::class)->seller($actor, $record), 403); + $context = $record->workflow_context; + $state = app(OfferAvailability::class)->record($record)['state']; + if ($data['action'] === 'withdraw') { + if (($context['offer_operation']['status'] ?? null) === 'withdrawn') { + return $record; + } + $this->save($record, $actor, $this->withdraw($context, 'owner'), 'offer_withdrawn', ['reason' => Crypt::encryptString($data['reason'] ?? '')]); + + return $record; + } + abort_unless(hash_equals(app(OpportunityManagement::class)->etag($record), $data['etag']), 409); + if ($data['action'] === 'request_reactivation') { + abort_unless(in_array($state, ['withdrawn', 'expired'], true), 409, 'A oferta não está retirada ou expirada.'); + $editorialState = app(PortalProfileAccess::class)->state($record); + abort_unless(isset($context['submission']) || in_array($editorialState, ['draft', 'needs_information'], true), 409, + 'A preparação anterior ainda precisa da vinculação editorial existente antes de abrir nova revisão.'); + $context['offer_operation'] = [...($context['offer_operation'] ?? []), 'status' => 'revalidation', 'cycle' => ($context['offer_operation']['cycle'] ?? 0) + 1, + 'requested_by' => $actor->id, 'requested_at' => now()->toIso8601String()]; + // Reopen the same editorial engine, retaining old submissions and decisions. + $record->workflow_context = $context; + $record->save(); + if (isset($context['submission'])) { + $record = app(OpportunityProfileWorkflow::class)->transition($actor, $id, ['etag' => app(OpportunityManagement::class)->etag($record), + 'action' => 'request_revision', 'reason' => 'Pedido explícito de nova validação para reactivar a oferta. '.($data['reason'] ?? '')]); + } + $this->save($record, $actor, $record->workflow_context, 'offer_reactivation_requested'); + + return $record; + } + abort_unless($state === 'revalidation' && app(PortalProfileAccess::class)->state($record) === 'approved', 409, 'É necessária nova validação editorial.'); + $snapshot = app(PrivateOfferPreparation::class)->snapshot($record); + abort_unless(($snapshot['payload']['operational_cycle'] ?? null) === $context['offer_operation']['cycle'] + && hash_equals($context['approval']['hash'] ?? '', $snapshot['hash']) + && ($context['submission']['payload']['operational_cycle'] ?? null) === $context['offer_operation']['cycle'], 409); + $context['offer_operation']['status'] = 'available'; + $context['offer_operation']['activated_hash'] = $snapshot['hash']; + $this->save($record, $actor, $context, 'offer_reactivated_privately'); + + return $record; + }); + } + + public function expire(int $id): bool + { + return DB::transaction(function () use ($id) { + $record = $this->offer($id, true); + $availability = app(OfferAvailability::class)->record($record); + if ($availability['state'] !== 'expired' || ($record->workflow_context['offer_operation']['status'] ?? null) === 'expired') { + return false; + } + $context = $record->workflow_context; + $context['offer_operation'] = [...($context['offer_operation'] ?? []), 'status' => 'expired', 'expired_at' => $availability['expires_at']]; + $this->save($record, null, $context, 'offer_expired', ['expires_at' => $availability['expires_at']]); + + return true; + }); + } + + public function canReport(User $actor, Opportunity $record): bool + { + if (app(PortalProfileAccess::class)->canView($actor, $record)) { + return true; + } + try { + app(OfferPublicCatalogue::class)->detail($record->id); + + return true; + } catch (HttpExceptionInterface $error) { + return false; + } + } + + /** @param array $input + * @return array */ + public function report(User $actor, int $id, array $input): array + { + $data = validator($input, ['client_key' => 'required|uuid', 'reason' => 'required|string|min:20|max:2000', 'confirm' => 'required|accepted'])->validate(); + + return DB::transaction(function () use ($actor, $id, $data) { + $record = $this->offer($id, true); + $context = $record->workflow_context; + $key = $data['client_key']; + $hash = hash('sha256', $data['reason']); + if (isset($context['offer_reports'][$key])) { + $previous = $context['offer_reports'][$key]; + abort_unless($previous['reporter_id'] === $actor->id, 404); + abort_unless($previous['command_hash'] === $hash, 409); + + return ['id' => $key, 'replayed' => true]; + } + abort_unless($this->canReport($actor, $record), 404); + try { + $source = app(OfferPublicCatalogue::class)->detail($id); + } catch (HttpExceptionInterface $error) { + $source = Arr::only(app(OpportunityManagement::class)->values($record), ['title', 'description', 'opportunity_category', 'private_offer']); + } + $task = app(OpportunityWorkflowTasks::class)->ensure($record, 'offer_report_'.$key, 'Denúncia da Oferta '.$record->reference); + $context['offer_reports'][$key] = ['id' => $key, 'reporter_id' => $actor->id, 'at' => now()->toIso8601String(), 'command_hash' => $hash, + 'reason' => Crypt::encryptString($data['reason']), 'version' => 1, 'status' => 'open', 'task_id' => $task, + 'source_snapshot' => Crypt::encryptString(json_encode($source, JSON_THROW_ON_ERROR)), + 'source_record_version' => $record->management_version, 'source_approval_hash' => $context['approval']['hash'] ?? null, + 'source_public_hash' => $context['offer_publication']['publication']['hash'] ?? null]; + $this->save($record, $actor, $context, 'offer_reported', ['report_id' => $key]); + + return ['id' => $key, 'replayed' => false]; + }); + } + + /** @param array $input */ + public function handleReport(User $actor, int $id, string $reportId, array $input): void + { + $data = validator($input, ['etag' => 'required|string|size:64', 'version' => 'required|integer|min:1', 'action' => 'required|in:start_review,resolve_no_withdrawal,resolve_withdrawal', + 'reason' => 'required|string|min:20|max:2000', 'confirm' => 'required|accepted'])->validate(); + DB::transaction(function () use ($actor, $id, $reportId, $data) { + $record = $this->offer($id, true); + abort_unless(app(PrivateOfferConfidentiality::class)->internal($actor, $record), 403); + abort_unless(hash_equals(app(OpportunityManagement::class)->etag($record), $data['etag']), 409); + $context = $record->workflow_context; + $report = $context['offer_reports'][$reportId] ?? null; + abort_unless($report !== null, 404); + abort_unless($report['version'] === (int) $data['version'] && in_array($report['status'], ['open', 'in_review'], true), 409); + if ($data['action'] === 'start_review') { + abort_unless($report['status'] === 'open', 409); + app(OpportunityWorkflowTasks::class)->assign($record, $report['task_id'], $actor); + $report['status'] = 'in_review'; + } else { + abort_unless($report['status'] === 'in_review' && $report['reviewer_id'] === $actor->id, 409, 'A denúncia deve ser tratada pelo revisor que a iniciou.'); + app(OpportunityWorkflowTasks::class)->complete($report['task_id'], 'Denúncia tratada; decisão no histórico privado.'); + $report['status'] = 'resolved'; + $report['resolution'] = $data['action']; + if ($data['action'] === 'resolve_withdrawal') { + $context = $this->withdraw($context, 'moderation'); + } + } + $report['reviewer_id'] = $actor->id; + $report['version']++; + $report['updated_at'] = now()->toIso8601String(); + $report['resolution_reason'] = Crypt::encryptString($data['reason']); + $report['history'][] = ['action' => $data['action'], 'actor_id' => $actor->id, 'at' => now()->toIso8601String(), 'reason' => Crypt::encryptString($data['reason'])]; + $context['offer_reports'][$reportId] = $report; + $this->save($record, $actor, $context, 'offer_report_'.$data['action'], ['report_id' => $reportId]); + }); + } + + /** @param array $input */ + public function highlight(User $actor, int $id, array $input): void + { + $data = validator($input, ['etag' => 'required|string|size:64', 'version' => 'required|integer|min:0', 'status' => 'required|in:draft,archived', + 'label' => 'required|string|max:150', 'starts_at' => 'nullable|date', 'ends_at' => 'nullable|required_with:starts_at|date|after:starts_at', + 'note' => 'required|string|min:20|max:1000', 'confirm' => 'required|accepted'])->validate(); + DB::transaction(function () use ($actor, $id, $data) { + $record = $this->offer($id, true); + abort_unless($this->publisher($actor, $record), 403); + $context = $record->workflow_context; + $before = $context['offer_highlight'] ?? null; + abort_unless(hash_equals(app(OpportunityManagement::class)->etag($record), $data['etag']) && (int) $data['version'] === ($before['version'] ?? 0), 409); + $plan = ['version' => (int) $data['version'] + 1, 'status' => $data['status'], 'label' => $data['label'], 'starts_at' => $data['starts_at'] ?? null, + 'ends_at' => $data['ends_at'] ?? null, 'note' => Crypt::encryptString($data['note']), 'actor_id' => $actor->id, 'at' => now()->toIso8601String()]; + $context['offer_highlight_history'][] = $before; + $context['offer_highlight'] = $plan; + $this->save($record, $actor, $context, 'offer_highlight_prepared', ['plan_version' => $plan['version'], 'status' => $plan['status']]); + }); + } +} diff --git a/app/Services/PrivateOfferPreparation.php b/app/Services/PrivateOfferPreparation.php index 931a9553..7d25da32 100644 --- a/app/Services/PrivateOfferPreparation.php +++ b/app/Services/PrivateOfferPreparation.php @@ -154,6 +154,10 @@ final class PrivateOfferPreparation if ($extended) { $payload['resources'] = $resources; } + $cycle = $record->workflow_context['offer_operation']['cycle'] ?? 0; + if ($cycle > 0) { + $payload['operational_cycle'] = $cycle; + } $normalize = function (mixed $value) use (&$normalize): mixed { if (is_object($value)) { $value = get_object_vars($value); @@ -169,7 +173,7 @@ final class PrivateOfferPreparation }; $payload = $normalize($payload); - $contract = $extended ? 'private-offer-editorial-v3' : 'private-offer-editorial-v2'; + $contract = $cycle > 0 ? 'private-offer-editorial-v4' : ($extended ? 'private-offer-editorial-v3' : 'private-offer-editorial-v2'); return ['payload' => $payload, 'hash' => hash('sha256', json_encode(['contract' => $contract, 'payload' => $payload], JSON_THROW_ON_ERROR)), 'record_version' => $record->management_version, 'contract_version' => $contract, 'resources' => $resources]; diff --git a/config/private_offers.php b/config/private_offers.php index 4b52c37d..505f7083 100644 --- a/config/private_offers.php +++ b/config/private_offers.php @@ -3,4 +3,8 @@ return [ // No legal text or operational NDA is supplied/approved. Never infer it from an upload. 'nda' => null, + // No definitive publication lifetime was decided. Freeze an explicit policy on approval. + 'validity_days' => null, + 'validity_policy_reference' => null, + 'expiry_scheduler_enabled' => false, ]; diff --git a/docs/investmoz/unification/traceability-and-manual.md b/docs/investmoz/unification/traceability-and-manual.md index 74f95514..359fac95 100644 --- a/docs/investmoz/unification/traceability-and-manual.md +++ b/docs/investmoz/unification/traceability-and-manual.md @@ -10185,3 +10185,17 @@ Controlo empresarial exige aceitação do exemplar exacto do NDA e autorização Os cinco testes territoriais foram investigados: dependiam de Inhambane/turismo previamente importados numa base vazia. O fixture cria apenas os mínimos sintéticos quando faltam, com guarda testing/loopback/*_test:55441 e transacção de rollback. Os seis casos passaram; nenhum cadastro operacional foi alterado. Os snapshots F4 vinculam particularidades/recursos ao contrato v3; Ofertas comuns anteriores conservam v2. Estado: implementado em QA, sem instalação, publicação efectiva, pagamentos, destaque comercial, migrações ou alterações em mne. Fase 5 não iniciada; módulo integral/Procuras parciais. Pendências: exemplar/versão/aprovação do NDA, identificação jurídica dos comprovativos admitidos, visual desktop/móvel e email real anteriores. GD ausente: derivadas do processador nativo continuam bloqueadas, sem simular processamento. Formatação global mantém 61 ficheiros exteriores ao delta. Relatório: docs/investmoz/v2/auditoria-portal/ofertas-fase4-entrega-qa-20261114.md. Recibo: outputs/offers-private-phase4-20261114/qa-result.json; contém resultados e hashes finais, sem somar execuções sobrepostas. Próximo passo: apreciação QA e disponibilização do NDA aprovado, mantendo o bloqueio de publicação. + +## 14/11/2026 — Ofertas Fase 5, fecho operacional em QA + +Base aceite com ressalvas e431de8b preservada, tal como 7691cab3, 91c18573, 1f2990e4 e plano6e302db6. F5 autorizada exclusivamente em QA. O dono pode retirar disponibilidade imediatamente, sem aprovação editorial ou bloqueio por etag antigo. Retirada/expiração excluem leituras públicas e fecham dados protegidos por NDA. Conteúdos, identificadores, originais e decisões não são apagados. + +Reactivar exige pedido no mesmo cadastro, novo ciclo operacional e nova submissão/revisão/aprovação no motor existente; só depois o dono repõe disponibilidade privada. O snapshot v4 inclui o ciclo, preservando v2/v3 anteriores. Uma publicação antiga não reaparece e grants NDA antigos não reabrem por causa dessa reactivação. A reposição de disponibilidade não publica. + +Validade independente do NDA: validity_days e validity_policy_reference inicialmente nulos, sem prazo definitivo presumido. Política congelada por hash; início na publicação da versão; configuração ou aprovação privada posterior não prolongam o exemplar público anterior. Expiração verificada em cada leitura, sem depender do agendador. API/cartão apresentam data apenas quando conhecida; CSV conserva o formato anterior e a mesma exclusão. Comando investmoz:offers-expire confere sem escrita; --confirm materializa com auditoria/idempotência. Agendamento permanece desligado. + +Denúncias fundamentadas ficam cifradas com origem, actor e versão, ligadas a tarefas CRM e ao Dashboard Colaborativo existentes. A entrada Ofertas — acompanhamento usa a mesma listagem privada e filtro de denúncias pendentes. Validador no âmbito, com MFA e sem conflito, analisa e conclui com fundamento, com ou sem retirada. Etags e versão da denúncia protegem a decisão; o proprietário não recebe os dados originais do denunciante. Retomas devolvem o recibo sem outra tarefa. Planos administrativos de destaque têm versão/histórico e só rascunho/arquivo, sob Publicador autorizado; não alteram is_featured, cobram ou executam pagamentos. + +Verificação: percurso novo inicialmente com 12 testes/151 asserções aprovados; regressão integrada final com 59 testes/780 asserções aprovados, com resultados no recibo outputs/offers-private-phase5-20261114/qa-result.json. Grupos sobrepostos não devem ser somados. Relatório técnico e matriz de aceitação das cinco fases: docs/investmoz/v2/auditoria-portal/ofertas-fase5-fecho-qa-20261114.md. A matriz RF-MN-003 existente foi actualizada, sem novo inventário geral. + +Estado: cinco fases preparadas em QA; F1-F4 aceites tecnicamente com ressalvas, F5 para apreciação; nenhuma instalação ou publicação efectiva. Mantêm-se pendências NDA/exemplar/versão/aprovação, tipos jurídicos de comprovativos, prazo final, avisos jurídicos, visual desktop/móvel, GD/WebP e email real F2. Formatação global mantém61ficheiros preexistentes fora do delta. NDA mantém configuração nula; sem migrações, alterações em mne, dados operacionais, pagamentos ou promoção comercial. Procuras/restante portal não encerrados por este lote. Próximo passo: apreciação QA e decisões documentais; instalação/activação/publicação aguardam autorização expressa. diff --git a/docs/investmoz/v2/auditoria-portal/matriz-requisitos.csv b/docs/investmoz/v2/auditoria-portal/matriz-requisitos.csv index 357391e4..cfcf8b23 100644 --- a/docs/investmoz/v2/auditoria-portal/matriz-requisitos.csv +++ b/docs/investmoz/v2/auditoria-portal/matriz-requisitos.csv @@ -163,7 +163,7 @@ RF-MIN-005,"Controlar cessão, mudança de controlo e aprovações.",upgrade v4 RF-MIN-006,Pesquisar e corresponder oportunidades mineiras.,upgrade v4 SHA f0b9815…; p. 3275; requirements-audit.csv,oportunidades,/api/v1/business-opportunities; /api/v1/business-opportunities/{slug}; /investidor/terra-duat; /investidor/terra-duat/novo; /investidor/terra-duat/{id}; /investidor/terra-duat/{id}/actualizacoes; /investidor/terra-duat/{id}/documentos; /investidor/terra-duat/{id}/documentos/{attachment}; /investidor/terra-duat/{id}/editar; /investidor/terra-duat/{id}/imprimir; /investidor/terra-duat/{id}/solicitar-apoio; /management/opportunities,"OpportunityManagement, OpportunityCanonicalContract/Response, OpportunityReviewMateriality, PortalProfileAccess, OpportunityResources. Capacidade existente associada ao domínio; correspondência integral desta cláusula só quando demonstrada na prova individual.","Ativo investemoz:5432, 295 tabelas, COUNTRY02/030000/040000 instalados; seis categorias e administração habilitadas. Leitura atual não demonstra esta cláusula individual. Legado, percurso integrado, modulares/ofertas e notificações desabilitados.",outputs/investmoz-audit-current-20261012/runtime-active.json; runtime-qa18445.json. Inventário/GET do domínio não demonstra esta cláusula individual.,Sem evidência suficiente,Reconciliar contrato/cláusula e provas por categoria; completar apenas falha/variante executável identificada. Response normativo não é completo. Critério individual original preservado; cobertura atual ainda não individualizada.,"030000→040000 já instalados; contratos/resources existentes, sessão/MFA, ClamD e worker para ficheiros; gates das seis categorias ON. Variantes modulares/offers continuam separadas. Nenhuma nova decisão identificada para reconciliar/demonstrar esta cláusula. Decisões específicas só após localizar a regra correspondente.","RF-MIN-006: Filtros incluem recurso, fase, título, localização, valor, modalidade e infra-estrutura; recomendação é explicável e revista por gestor.",P2,RF-MIN-006: Relatório 25/09 conservado; estado histórico não promove conclusão,RF-MIN-006,1,"[{""id"":""RF-MIN-006"",""pedido"":""Pesquisar e corresponder oportunidades mineiras."",""fontes"":[""upgrade v4 SHA f0b9815…; p. 3275; requirements-audit.csv""],""aceitacao"":""Filtros incluem recurso, fase, título, localização, valor, modalidade e infra-estrutura; recomendação é explicável e revista por gestor."",""estado"":""parcial"",""variantes"":""Relatório 25/09 conservado; estado histórico não promove conclusão"",""modulo"":""oportunidades""}]",Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Texto exatamente equivalente no mesmo módulo; sem fusão semântica aproximada,parcial,public.opportunities: 6 registos; public.opportunity_images: tabela ausente; public.opportunity_documents: tabela ausente; public.opportunity_location_drafts: tabela ausente,"A evidência existente é do domínio ou uma declaração histórica; não identifica a demonstração integral desta cláusula. Não afirmar implementado/parcial apenas por componente, menu ou teste global.",Reconciliar contrato/cláusula e provas por categoria; completar apenas falha/variante executável identificada. Response normativo não é completo. Critério individual original preservado; cobertura atual ainda não individualizada.,"OpportunityManagement, OpportunityCanonicalContract/Response, OpportunityReviewMateriality, PortalProfileAccess, OpportunityResources","030000→040000 já instalados; contratos/resources existentes, sessão/MFA, ClamD e worker para ficheiros; gates das seis categorias ON. Variantes modulares/offers continuam separadas.",Nenhuma nova decisão identificada para reconciliar/demonstrar esta cláusula. Decisões específicas só após localizar a regra correspondente.,Reconciliação e prova individual antes de estimar alteração,Estimativa partilhada no lote correspondente de visao-consolidada-modulos.md; não somar por linha/cláusula/campo. Grupo: oportunidades. Esforço de código adicional só após lacuna reproduzida.,outputs/investmoz-audit-current-20261012/runtime-active.json; runtime-qa18445.json. Inventário/GET do domínio não demonstra esta cláusula individual.,oportunidades,12/10/2026 — reconciliação e HTTP; nova inspeção visual bloqueada,,,Verificar individualmente este critério no contrato e percurso aplicável; a matriz inicial conserva texto/fonte sem o promover a aceitação.,"QA de UI quando browser disponível; reutilizar o domínio existente. Dados/decisões específicos no backlog, sem segunda validação de fonte oficial.",app/Services/OpportunityManagement.php; app/Services/OpportunityResources.php; resources/js/pages/management/opportunity-draft.tsx — componentes do domínio encontrados; correspondência integral deste requisito não presumida,"OpportunityManagementTest, OpportunityResourcesTest, OpportunityOriginReviewTest; suite global QA e GET por perfil disponíveis. Evidência do domínio não demonstra integralmente esta cláusula; UI individual pendente.",Não demonstrado individualmente; localizar critério e executar em QA.,,,,"0,5–2 h de análise/prova por cláusula sem correspondência, agrupada por contrato/consumidor; não somar estas faixas como projetos independentes. Implementação indeterminada até localizar lacuna.",,, RF-MN-001,Registar utilizadores singulares e colectivos com verificação de contactos.,upgrade v4 SHA f0b9815…; p. 2957; requirements-audit.csv | portal do investidor\upgrade do portal de investimentos.docx; SHA 471012abb0f56377956f4ab822fe3186e2561bee0471508a5d807091f2e0154f; w:p 573 | portal do investidor\Portal V4\upgrade do portal de investimentos v6 programacao.docx; SHA e1169a7743b13ff6c950d3ffd4672a2afea32e81da84885e11154b7d8de9241e; w:p 3409 | portal do investidor\Portal V4\upgrade do portal de investimentos v1.docx; SHA f70ce3075523c6278e00dce15b9c7acf2b690679e16959ae5f7d007e754cfc6e; w:p 580 | portal do investidor\Portal V4\upgrade do portal de investimentos v2.docx; SHA 51b8f0ad3296d1064a5c12b450874fc72640869f10a164319cd7f3153dd3a086; w:p 2214 | portal do investidor\Portal V4\upgrade do portal de investimentos v5 programacao.docx; SHA edece80c0519537244f8a8b86456af0aa92de1c526f791bb0ee9b85af7a7e8b2; w:p 3409,CRM/solicitações,//; /Simulador; /api/v1/documents/recommendations; /api/v1/investment/submissions; /api/v1/investment/submissions/{id}; /api/v1/investment/submissions/{id}/attachments; /api/v1/investment/submissions/{id}/attachments/{attachment}; /api/v1/investment/submissions/{id}/submit; /apoio; /balcao-virtual; /como-investir; /documentos,"investors/contacts/leads/tasks, OpportunityContacts, acompanhamento interno existente. Capacidade existente associada ao domínio; correspondência integral desta cláusula só quando demonstrada na prova individual.","Ativo investemoz:5432, 295 tabelas, COUNTRY02/030000/040000 instalados; seis categorias e administração habilitadas. Leitura atual não demonstra esta cláusula individual. Legado, percurso integrado, modulares/ofertas e notificações desabilitados.",outputs/investmoz-audit-current-20261012/runtime-active.json; runtime-qa18445.json. Inventário/GET do domínio não demonstra esta cláusula individual.,Sem evidência suficiente,"Converter sem duplicar, tarefa/responsável/próxima ação recuperáveis; corrigir apenas falha comprovada. Critério individual original preservado; cobertura atual ainda não individualizada.",Relações mestre/contexto e autorizações; não depende de CRM externo. C131 sóQA. Nenhuma nova decisão identificada para reconciliar/demonstrar esta cláusula. Decisões específicas só após localizar a regra correspondente.,RF-MN-001: Conta só é activada após confirmação do correio electrónico ou telefone; tentativas e falhas ficam registadas. | MASTER-471012ab-0051: Registar utilizadores singulares e colectivos com verificação de contactos. | MASTER-e1169a77-0201: Registar utilizadores singulares e colectivos com verificação de contactos. | MASTER-f70ce307-0057: Registar utilizadores singulares e colectivos com verificação de contactos. | MASTER-51b8f0ad-0137: Registar utilizadores singulares e colectivos com verificação de contactos. | MASTER-edece80c-0201: Registar utilizadores singulares e colectivos com verificação de contactos.,P2,RF-MN-001: Relatório 25/09 conservado; estado histórico não promove conclusão | MASTER-471012ab-0051: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-e1169a77-0201: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-f70ce307-0057: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-51b8f0ad-0137: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-edece80c-0201: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas,RF-MN-001 | MASTER-471012ab-0051 | MASTER-e1169a77-0201 | MASTER-f70ce307-0057 | MASTER-51b8f0ad-0137 | MASTER-edece80c-0201,6,"[{""id"":""RF-MN-001"",""pedido"":""Registar utilizadores singulares e colectivos com verificação de contactos."",""fontes"":[""upgrade v4 SHA f0b9815…; p. 2957; requirements-audit.csv""],""aceitacao"":""Conta só é activada após confirmação do correio electrónico ou telefone; tentativas e falhas ficam registadas."",""estado"":""parcial"",""variantes"":""Relatório 25/09 conservado; estado histórico não promove conclusão"",""modulo"":""CRM/solicitações""},{""id"":""MASTER-471012ab-0051"",""pedido"":""Registar utilizadores singulares e colectivos com verificação de contactos."",""fontes"":[""portal do investidor\\upgrade do portal de investimentos.docx; SHA 471012abb0f56377956f4ab822fe3186e2561bee0471508a5d807091f2e0154f; w:p 573""],""aceitacao"":""Registar utilizadores singulares e colectivos com verificação de contactos."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""CRM/solicitações""},{""id"":""MASTER-e1169a77-0201"",""pedido"":""Registar utilizadores singulares e colectivos com verificação de contactos."",""fontes"":[""portal do investidor\\Portal V4\\upgrade do portal de investimentos v6 programacao.docx; SHA e1169a7743b13ff6c950d3ffd4672a2afea32e81da84885e11154b7d8de9241e; w:p 3409""],""aceitacao"":""Registar utilizadores singulares e colectivos com verificação de contactos."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""CRM/solicitações""},{""id"":""MASTER-f70ce307-0057"",""pedido"":""Registar utilizadores singulares e colectivos com verificação de contactos."",""fontes"":[""portal do investidor\\Portal V4\\upgrade do portal de investimentos v1.docx; SHA f70ce3075523c6278e00dce15b9c7acf2b690679e16959ae5f7d007e754cfc6e; w:p 580""],""aceitacao"":""Registar utilizadores singulares e colectivos com verificação de contactos."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""CRM/solicitações""},{""id"":""MASTER-51b8f0ad-0137"",""pedido"":""Registar utilizadores singulares e colectivos com verificação de contactos."",""fontes"":[""portal do investidor\\Portal V4\\upgrade do portal de investimentos v2.docx; SHA 51b8f0ad3296d1064a5c12b450874fc72640869f10a164319cd7f3153dd3a086; w:p 2214""],""aceitacao"":""Registar utilizadores singulares e colectivos com verificação de contactos."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""CRM/solicitações""},{""id"":""MASTER-edece80c-0201"",""pedido"":""Registar utilizadores singulares e colectivos com verificação de contactos."",""fontes"":[""portal do investidor\\Portal V4\\upgrade do portal de investimentos v5 programacao.docx; SHA edece80c0519537244f8a8b86456af0aa92de1c526f791bb0ee9b85af7a7e8b2; w:p 3409""],""aceitacao"":""Registar utilizadores singulares e colectivos com verificação de contactos."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""CRM/solicitações""}]",Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Texto exatamente equivalente no mesmo módulo; sem fusão semântica aproximada,parcial,public.interest_requests: 4 registos; public.leads: 0 registos; public.investors: 3 registos,"A evidência existente é do domínio ou uma declaração histórica; não identifica a demonstração integral desta cláusula. Não afirmar implementado/parcial apenas por componente, menu ou teste global.","Converter sem duplicar, tarefa/responsável/próxima ação recuperáveis; corrigir apenas falha comprovada. Critério individual original preservado; cobertura atual ainda não individualizada.","investors/contacts/leads/tasks, OpportunityContacts, acompanhamento interno existente",Relações mestre/contexto e autorizações; não depende de CRM externo. C131 sóQA.,Nenhuma nova decisão identificada para reconciliar/demonstrar esta cláusula. Decisões específicas só após localizar a regra correspondente.,Reconciliação e prova individual antes de estimar alteração,Estimativa partilhada no lote correspondente de visao-consolidada-modulos.md; não somar por linha/cláusula/campo. Grupo: acompanhamento. Esforço de código adicional só após lacuna reproduzida.,outputs/investmoz-audit-current-20261012/runtime-active.json; runtime-qa18445.json. Inventário/GET do domínio não demonstra esta cláusula individual.,acompanhamento,12/10/2026 — reconciliação e HTTP; nova inspeção visual bloqueada,,,Verificar individualmente este critério no contrato e percurso aplicável; a matriz inicial conserva texto/fonte sem o promover a aceitação.,"QA de UI quando browser disponível; reutilizar o domínio existente. Dados/decisões específicos no backlog, sem segunda validação de fonte oficial.",app/Http/Controllers/CrmController.php; app/Services/DeclaredOriginStatistics.php — componentes do domínio encontrados; correspondência integral deste requisito não presumida,"InterestSubmissionTest, InvestmentWorkspaceTest, PortalOriginCompatibilityTest; suite global QA e GET por perfil disponíveis. Evidência do domínio não demonstra integralmente esta cláusula; UI individual pendente.",Não demonstrado individualmente; localizar critério e executar em QA.,,,,"0,5–2 h de análise/prova por cláusula sem correspondência, agrupada por contrato/consumidor; não somar estas faixas como projetos independentes. Implementação indeterminada até localizar lacuna.",,, RF-MN-002,"Gerir perfis, representantes, funções e organizações.",upgrade v4 SHA f0b9815…; p. 2961; requirements-audit.csv,governação/outros,/api/v1/investment/admin/interests/{interest}/status; /management/history; /management/interests; /management/interests/{interest}; /management/interests/{interest}/consent/contact; /management/interests/{interest}/consent/proponent; /management/opportunities; /management/opportunities/create; /management/opportunities/drafts; /management/opportunities/{id}/draft; /management/opportunities/{id}/edit; /management/opportunities/{id}/lifecycle,"Entidades, schema/configuração e serviços existentes; correspondência individual por estabelecer. Capacidade existente associada ao domínio; correspondência integral desta cláusula só quando demonstrada na prova individual.","Ativo investemoz:5432, 295 tabelas, COUNTRY02/030000/040000 instalados; seis categorias e administração habilitadas. Leitura atual não demonstra esta cláusula individual. Legado, percurso integrado, modulares/ofertas e notificações desabilitados.","outputs/investmoz-audit-current-20261012/runtime-active.json; runtime-qa18445.json. Inventário/GET do domínio não demonstra esta cláusula individual. | 02/11/2026: profile-json preserva erros por campo e reautenticacao em falhas HTML/419 e rejeita JSON invalido; contexto de investimento rejeita falso sucesso HTTP 200 e liberta keepalive, sem repetir escritas. 26 testes focados, TypeScript e build aprovados. Prova local parcial; sem dados/permissoes/publicacao. | Retoma 02/11/2026: profileJson GET independente de cookie CSRF malformado; POST recusa antes do transporte com mensagem de recuperacao. Preservadas validacao e credenciais. 14 testes focados aprovados, incluindo separadores de cookie sem espaco; sem mudanca de direitos.",Sem evidência suficiente,Localizar obrigação verificável e consumidores/contrato atuais antes de estimar implementação ou declarar conclusão. Critério individual original preservado; cobertura atual ainda não individualizada.,"Ler secção/versão correspondente; distinguir requisito funcional, decisão e exemplo. Fontes oficiais já aceites. Nenhuma nova decisão identificada para reconciliar/demonstrar esta cláusula. Decisões específicas só após localizar a regra correspondente.",RF-MN-002: Uma organização pode ter vários utilizadores; cada acção identifica o utilizador e a função.,P2,RF-MN-002: Relatório 25/09 conservado; estado histórico não promove conclusão,RF-MN-002,1,"[{""id"":""RF-MN-002"",""pedido"":""Gerir perfis, representantes, funções e organizações."",""fontes"":[""upgrade v4 SHA f0b9815…; p. 2961; requirements-audit.csv""],""aceitacao"":""Uma organização pode ter vários utilizadores; cada acção identifica o utilizador e a função."",""estado"":""parcial"",""variantes"":""Relatório 25/09 conservado; estado histórico não promove conclusão"",""modulo"":""governação/outros""}]",Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Texto exatamente equivalente no mesmo módulo; sem fusão semântica aproximada,parcial,Requisito transversal; consultar estado-ativo-after.json,"A evidência existente é do domínio ou uma declaração histórica; não identifica a demonstração integral desta cláusula. Não afirmar implementado/parcial apenas por componente, menu ou teste global.",Localizar obrigação verificável e consumidores/contrato atuais antes de estimar implementação ou declarar conclusão. Critério individual original preservado; cobertura atual ainda não individualizada.,"Entidades, schema/configuração e serviços existentes; correspondência individual por estabelecer","Ler secção/versão correspondente; distinguir requisito funcional, decisão e exemplo. Fontes oficiais já aceites.",Nenhuma nova decisão identificada para reconciliar/demonstrar esta cláusula. Decisões específicas só após localizar a regra correspondente.,Reconciliação e prova individual antes de estimar alteração,Estimativa partilhada no lote correspondente de visao-consolidada-modulos.md; não somar por linha/cláusula/campo. Grupo: governação e demais cláusulas. Esforço de código adicional só após lacuna reproduzida.,outputs/investmoz-audit-current-20261012/runtime-active.json; runtime-qa18445.json. Inventário/GET do domínio não demonstra esta cláusula individual.,governação e demais cláusulas,12/10/2026 — reconciliação e HTTP; nova inspeção visual bloqueada,,,Verificar individualmente este critério no contrato e percurso aplicável; a matriz inicial conserva texto/fonte sem o promover a aceitação.,"QA de UI quando browser disponível; reutilizar o domínio existente. Dados/decisões específicos no backlog, sem segunda validação de fonte oficial.",AGENTS.md — componentes do domínio encontrados; correspondência integral deste requisito não presumida,; suite global QA e GET por perfil disponíveis. Evidência do domínio não demonstra integralmente esta cláusula; UI individual pendente.,Não demonstrado individualmente; localizar critério e executar em QA.,,,,"0,5–2 h de análise/prova por cláusula sem correspondência, agrupada por contrato/consumidor; não somar estas faixas como projetos independentes. Implementação indeterminada até localizar lacuna.",,, -RF-MN-003,"Criar, guardar, editar e submeter ofertas e procuras.",upgrade v4 SHA f0b9815…; p. 2965; requirements-audit.csv | portal do investidor\upgrade do portal de investimentos.docx; SHA 471012abb0f56377956f4ab822fe3186e2561bee0471508a5d807091f2e0154f; w:p 581 | portal do investidor\Portal V4\upgrade do portal de investimentos v6 programacao.docx; SHA e1169a7743b13ff6c950d3ffd4672a2afea32e81da84885e11154b7d8de9241e; w:p 3417 | portal do investidor\Portal V4\upgrade do portal de investimentos v1.docx; SHA f70ce3075523c6278e00dce15b9c7acf2b690679e16959ae5f7d007e754cfc6e; w:p 588 | portal do investidor\Portal V4\upgrade do portal de investimentos v2.docx; SHA 51b8f0ad3296d1064a5c12b450874fc72640869f10a164319cd7f3153dd3a086; w:p 2222 | portal do investidor\Portal V4\upgrade do portal de investimentos v5 programacao.docx; SHA edece80c0519537244f8a8b86456af0aa92de1c526f791bb0ee9b85af7a7e8b2; w:p 3417,governação/outros,/api/v1/investment/admin/interests/{interest}/status; /management/history; /management/interests; /management/interests/{interest}; /management/interests/{interest}/consent/contact; /management/interests/{interest}/consent/proponent; /management/opportunities; /management/opportunities/create; /management/opportunities/drafts; /management/opportunities/{id}/draft; /management/opportunities/{id}/edit; /management/opportunities/{id}/lifecycle; /ofertas; /ofertas/{id}; /api/v1/offers; /api/v1/offers/{id}; /api/v1/offers/map; /api/v1/offers/export; /minhas-oportunidades/ofertas/{id}/recursos; /minhas-oportunidades/ofertas/{id}/confidencialidade; /minhas-oportunidades/ofertas/{id}/confidencial; /minhas-oportunidades/ofertas/{id}/nda,"Entidades, schema/configuração e serviços existentes; correspondência individual por estabelecer. Capacidade existente associada ao domínio; correspondência integral desta cláusula só quando demonstrada na prova individual. | 14/11/2026 - Ofertas F1: oportunidades como cadastro principal; criar, guardar, recuperar, editar e submeter em privado; autorizacao existente, CRM e localizacao reutilizados; sem menu, publicacao ou instalacao. | 14/11/2026 F2 autorizada: circuito editorial privado no workflow e Dashboard existentes; revisao/atribuicao/rejeicao/correccao/reenvio/aprovacao interna; decisoes por snapshot/hash/ciclo, tarefas, auditoria e notificacoes. Sem publicacao ou instalacao. | 14/11/2026 F3 autorizada: consulta read-only da projeccao publica separada, filtros por categoria/localizacao/preco/moeda, grelha/lista/mapa administrativo e CSV. Barreira efectiva fechada; apenas amostras sinteticas em QA isolado podem ser consumidas por injeccao de teste. | 14/11/2026 F4 autorizada, campos propostos confirmados: particularidades opcionais de empresas/imoveis no mesmo rascunho; seis fotografias activas com edicao/retirada/restauro; originais cifrados e scan existente; comprovativos exclusivamente para validadores internos com ambito/MFA. NDA e autorizacao expressa do vendedor vinculados a versao/hash, revogacao e auditoria; exemplar juridico real pendente, configuracao nula. Sem publicacao ou instalacao.","Ativo investemoz:5432, 295 tabelas, COUNTRY02/030000/040000 instalados; seis categorias e administração habilitadas. Leitura atual não demonstra esta cláusula individual. Legado, percurso integrado, modulares/ofertas e notificações desabilitados.","outputs/investmoz-audit-current-20261012/runtime-active.json; runtime-qa18445.json. Inventário/GET do domínio não demonstra esta cláusula individual. | QA: PrivateOfferPreparationTest.php; regressao 35 testes/548 assercoes e passagem final focada 16/172 (grupos sobrepostos, nao somar). outputs/offers-private-phase1-20261114/qa-result.json. | F2 QA: 35 testes/499 assercoes; final focado 11/160; complemento contribuidores 1/14 e HTML compilado 1/10 aprovados, grupos sobrepostos. tests/Feature/PrivateOfferEditorialTest.php; outputs/offers-private-phase2-20261114/qa-result.json. | F3 QA: tests/Feature/OfferPublicCatalogueTest.php; outputs/offers-public-phase3-20261114/qa-result.json; sem instalacao ou publicacao real. | F4: tests/Feature/PrivateOfferConfidentialityTest.php; outputs/offers-private-phase4-20261114/qa-result.json. Cinco casos territoriais desbloqueados com fixture sintetico guardado em transaccao QA; sem dados operacionais.",Parcial - Ofertas F1/F2/F3 aceites tecnicamente; F4 implementada em QA com NDA real pendente; publicacao bloqueada; nao instalada,"NDA aprovado: exemplar, versao e referencia de aprovacao por disponibilizar. Tipos juridicos de comprovativos por identificar; sem certificacao de titularidade. Visual/movel e email real F2 pendentes. Derivadas: GD/WebP indisponivel no PHP QA. Publicacao efectiva bloqueada; F5 nao iniciada. Procuras permanece por implementar.",Autorizacao expressa para Fase 3 e para instalacao; designacoes operacionais e configuracao de entrega reais apenas quando aplicaveis. Sem novos papeis ou permissoes por inferencia.,"RF-MN-003: Rascunho persiste; submissão bloqueia campos essenciais e gera número de protocolo. | MASTER-471012ab-0052: Criar, guardar, editar e submeter ofertas e procuras. | MASTER-e1169a77-0202: Criar, guardar, editar e submeter ofertas e procuras. | MASTER-f70ce307-0058: Criar, guardar, editar e submeter ofertas e procuras. | MASTER-51b8f0ad-0138: Criar, guardar, editar e submeter ofertas e procuras. | MASTER-edece80c-0202: Criar, guardar, editar e submeter ofertas e procuras.",P2,RF-MN-003: Relatório 25/09 conservado; estado histórico não promove conclusão | MASTER-471012ab-0052: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-e1169a77-0202: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-f70ce307-0058: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-51b8f0ad-0138: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-edece80c-0202: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas,RF-MN-003 | MASTER-471012ab-0052 | MASTER-e1169a77-0202 | MASTER-f70ce307-0058 | MASTER-51b8f0ad-0138 | MASTER-edece80c-0202,6,"[{""id"":""RF-MN-003"",""pedido"":""Criar, guardar, editar e submeter ofertas e procuras."",""fontes"":[""upgrade v4 SHA f0b9815…; p. 2965; requirements-audit.csv""],""aceitacao"":""Rascunho persiste; submissão bloqueia campos essenciais e gera número de protocolo."",""estado"":""parcial"",""variantes"":""Relatório 25/09 conservado; estado histórico não promove conclusão"",""modulo"":""governação/outros""},{""id"":""MASTER-471012ab-0052"",""pedido"":""Criar, guardar, editar e submeter ofertas e procuras."",""fontes"":[""portal do investidor\\upgrade do portal de investimentos.docx; SHA 471012abb0f56377956f4ab822fe3186e2561bee0471508a5d807091f2e0154f; w:p 581""],""aceitacao"":""Criar, guardar, editar e submeter ofertas e procuras."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""},{""id"":""MASTER-e1169a77-0202"",""pedido"":""Criar, guardar, editar e submeter ofertas e procuras."",""fontes"":[""portal do investidor\\Portal V4\\upgrade do portal de investimentos v6 programacao.docx; SHA e1169a7743b13ff6c950d3ffd4672a2afea32e81da84885e11154b7d8de9241e; w:p 3417""],""aceitacao"":""Criar, guardar, editar e submeter ofertas e procuras."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""},{""id"":""MASTER-f70ce307-0058"",""pedido"":""Criar, guardar, editar e submeter ofertas e procuras."",""fontes"":[""portal do investidor\\Portal V4\\upgrade do portal de investimentos v1.docx; SHA f70ce3075523c6278e00dce15b9c7acf2b690679e16959ae5f7d007e754cfc6e; w:p 588""],""aceitacao"":""Criar, guardar, editar e submeter ofertas e procuras."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""},{""id"":""MASTER-51b8f0ad-0138"",""pedido"":""Criar, guardar, editar e submeter ofertas e procuras."",""fontes"":[""portal do investidor\\Portal V4\\upgrade do portal de investimentos v2.docx; SHA 51b8f0ad3296d1064a5c12b450874fc72640869f10a164319cd7f3153dd3a086; w:p 2222""],""aceitacao"":""Criar, guardar, editar e submeter ofertas e procuras."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""},{""id"":""MASTER-edece80c-0202"",""pedido"":""Criar, guardar, editar e submeter ofertas e procuras."",""fontes"":[""portal do investidor\\Portal V4\\upgrade do portal de investimentos v5 programacao.docx; SHA edece80c0519537244f8a8b86456af0aa92de1c526f791bb0ee9b85af7a7e8b2; w:p 3417""],""aceitacao"":""Criar, guardar, editar e submeter ofertas e procuras."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""}]","QA - preparacao, revisao e historico privados; filtros e acesso pelo Dashboard existente; visual manual pendente",QA - operacoes autenticadas /minhas-oportunidades/ofertas e /{id}/revisao; MFA/ambito/conflitos verificados no servidor; publicacao recusada,"QA - cadastro Oportunidades, tarefas/auditoria/notificacoes nativos; snapshots e hashes por ciclo; sem tabelas ou migracoes novas","QA - recuperar oferta, correccao, decisao e versoes anteriores; conflitos, rollback e indisponibilidade sem downgrade | F4: guardar/recuperar particularidades; fotografias e provas no recurso existente; historico de aceitacao/autorizacao/retirada/revogacao por exemplar e versao, sem substituir dados ou decisoes.","QA - aprovacao interna privada distinta de publicacao; versao publica nativa anterior preservada | F3: projecao publica anterior preservada durante edicao/rejeicao da nova revisao; sem contacto, anexos ou coordenadas do negocio; moeda sem conversao. | F4: dados empresariais reservados so depois de NDA + autorizacao expressa do vendedor; comprovativos nunca entregues ao interessado.",Texto exatamente equivalente no mesmo módulo; sem fusão semântica aproximada,parcial,Requisito transversal; consultar estado-ativo-after.json,"A evidência existente é do domínio ou uma declaração histórica; não identifica a demonstração integral desta cláusula. Não afirmar implementado/parcial apenas por componente, menu ou teste global.",Localizar obrigação verificável e consumidores/contrato atuais antes de estimar implementação ou declarar conclusão. Critério individual original preservado; cobertura atual ainda não individualizada.,"Entidades, schema/configuração e serviços existentes; correspondência individual por estabelecer","Ler secção/versão correspondente; distinguir requisito funcional, decisão e exemplo. Fontes oficiais já aceites.",Nenhuma nova decisão identificada para reconciliar/demonstrar esta cláusula. Decisões específicas só após localizar a regra correspondente.,Reconciliação e prova individual antes de estimar alteração,Estimativa partilhada no lote correspondente de visao-consolidada-modulos.md; não somar por linha/cláusula/campo. Grupo: governação e demais cláusulas. Esforço de código adicional só após lacuna reproduzida.,outputs/investmoz-audit-current-20261012/runtime-active.json; runtime-qa18445.json. Inventário/GET do domínio não demonstra esta cláusula individual.,governação e demais cláusulas,12/10/2026 — reconciliação e HTTP; nova inspeção visual bloqueada,,,Verificar individualmente este critério no contrato e percurso aplicável; a matriz inicial conserva texto/fonte sem o promover a aceitação.,"QA de UI quando browser disponível; reutilizar o domínio existente. Dados/decisões específicos no backlog, sem segunda validação de fonte oficial.",AGENTS.md — componentes do domínio encontrados; correspondência integral deste requisito não presumida,; suite global QA e GET por perfil disponíveis. Evidência do domínio não demonstra integralmente esta cláusula; UI individual pendente.,Não demonstrado individualmente; localizar critério e executar em QA.,,,,"0,5–2 h de análise/prova por cláusula sem correspondência, agrupada por contrato/consumidor; não somar estas faixas como projetos independentes. Implementação indeterminada até localizar lacuna.",,, +RF-MN-003,"Criar, guardar, editar e submeter ofertas e procuras.",upgrade v4 SHA f0b9815…; p. 2965; requirements-audit.csv | portal do investidor\upgrade do portal de investimentos.docx; SHA 471012abb0f56377956f4ab822fe3186e2561bee0471508a5d807091f2e0154f; w:p 581 | portal do investidor\Portal V4\upgrade do portal de investimentos v6 programacao.docx; SHA e1169a7743b13ff6c950d3ffd4672a2afea32e81da84885e11154b7d8de9241e; w:p 3417 | portal do investidor\Portal V4\upgrade do portal de investimentos v1.docx; SHA f70ce3075523c6278e00dce15b9c7acf2b690679e16959ae5f7d007e754cfc6e; w:p 588 | portal do investidor\Portal V4\upgrade do portal de investimentos v2.docx; SHA 51b8f0ad3296d1064a5c12b450874fc72640869f10a164319cd7f3153dd3a086; w:p 2222 | portal do investidor\Portal V4\upgrade do portal de investimentos v5 programacao.docx; SHA edece80c0519537244f8a8b86456af0aa92de1c526f791bb0ee9b85af7a7e8b2; w:p 3417,governação/outros,/api/v1/investment/admin/interests/{interest}/status; /management/history; /management/interests; /management/interests/{interest}; /management/interests/{interest}/consent/contact; /management/interests/{interest}/consent/proponent; /management/opportunities; /management/opportunities/create; /management/opportunities/drafts; /management/opportunities/{id}/draft; /management/opportunities/{id}/edit; /management/opportunities/{id}/lifecycle; /ofertas; /ofertas/{id}; /api/v1/offers; /api/v1/offers/{id}; /api/v1/offers/map; /api/v1/offers/export; /minhas-oportunidades/ofertas/{id}/recursos; /minhas-oportunidades/ofertas/{id}/confidencialidade; /minhas-oportunidades/ofertas/{id}/confidencial; /minhas-oportunidades/ofertas/{id}/nda; /minhas-oportunidades/ofertas/{id}/operacoes; /minhas-oportunidades/ofertas/{id}/denunciar; /minhas-oportunidades/ofertas/{id}/denuncias; /minhas-oportunidades/ofertas/{id}/destaque-administrativo,"Entidades, schema/configuração e serviços existentes; correspondência individual por estabelecer. Capacidade existente associada ao domínio; correspondência integral desta cláusula só quando demonstrada na prova individual. | 14/11/2026 - Ofertas F1: oportunidades como cadastro principal; criar, guardar, recuperar, editar e submeter em privado; autorizacao existente, CRM e localizacao reutilizados; sem menu, publicacao ou instalacao. | 14/11/2026 F2 autorizada: circuito editorial privado no workflow e Dashboard existentes; revisao/atribuicao/rejeicao/correccao/reenvio/aprovacao interna; decisoes por snapshot/hash/ciclo, tarefas, auditoria e notificacoes. Sem publicacao ou instalacao. | 14/11/2026 F3 autorizada: consulta read-only da projeccao publica separada, filtros por categoria/localizacao/preco/moeda, grelha/lista/mapa administrativo e CSV. Barreira efectiva fechada; apenas amostras sinteticas em QA isolado podem ser consumidas por injeccao de teste. | 14/11/2026 F4 autorizada, campos propostos confirmados: particularidades opcionais de empresas/imoveis no mesmo rascunho; seis fotografias activas com edicao/retirada/restauro; originais cifrados e scan existente; comprovativos exclusivamente para validadores internos com ambito/MFA. NDA e autorizacao expressa do vendedor vinculados a versao/hash, revogacao e auditoria; exemplar juridico real pendente, configuracao nula. Sem publicacao ou instalacao. | 14/11/2026 F5 autorizada em QA: retirada imediata pelo dono, novo ciclo/v4 e aprovacao antes da reactivacao; validade congelada por versao, prazo configuravel nulo por defeito, expiracao independente de NDA/cron; denuncias cifradas com origem versionada, tarefas e Dashboard existentes; planos administrativos de destaque sem efeitos comerciais. Publicacao e instalacao bloqueadas.","Ativo investemoz:5432, 295 tabelas, COUNTRY02/030000/040000 instalados; seis categorias e administração habilitadas. Leitura atual não demonstra esta cláusula individual. Legado, percurso integrado, modulares/ofertas e notificações desabilitados.","outputs/investmoz-audit-current-20261012/runtime-active.json; runtime-qa18445.json. Inventário/GET do domínio não demonstra esta cláusula individual. | QA: PrivateOfferPreparationTest.php; regressao 35 testes/548 assercoes e passagem final focada 16/172 (grupos sobrepostos, nao somar). outputs/offers-private-phase1-20261114/qa-result.json. | F2 QA: 35 testes/499 assercoes; final focado 11/160; complemento contribuidores 1/14 e HTML compilado 1/10 aprovados, grupos sobrepostos. tests/Feature/PrivateOfferEditorialTest.php; outputs/offers-private-phase2-20261114/qa-result.json. | F3 QA: tests/Feature/OfferPublicCatalogueTest.php; outputs/offers-public-phase3-20261114/qa-result.json; sem instalacao ou publicacao real. | F4: tests/Feature/PrivateOfferConfidentialityTest.php; outputs/offers-private-phase4-20261114/qa-result.json. Cinco casos territoriais desbloqueados com fixture sintetico guardado em transaccao QA; sem dados operacionais. | F5: tests/Feature/PrivateOfferOperationsTest.php; outputs/offers-private-phase5-20261114/qa-result.json. Matriz de aceitacao das cinco fases no relatorio ofertas-fase5-fecho-qa-20261114.md; provas sinteticas e regressao afectada, sem testes gerais de outros modulos.",Parcial - Ofertas F1-F4 tecnicamente aceites com ressalvas; F5 implementada em QA para apreciacao; cinco fases nao instaladas; publicacao efectiva bloqueada,"Dependencias para uso publico: exemplar/versao/aprovacao do NDA; tipos juridicos de comprovativos; prazo/politica final de validade; avisos juridicos, visual/movel, GD/WebP e email real F2. Politica comercial/destaque activo nao definidos e nao activados. Publicacao/instalacao exigem autorizacao expressa. Procuras permanece por implementar.",Autorizacao expressa para Fase 3 e para instalacao; designacoes operacionais e configuracao de entrega reais apenas quando aplicaveis. Sem novos papeis ou permissoes por inferencia.,"RF-MN-003: Rascunho persiste; submissão bloqueia campos essenciais e gera número de protocolo. | MASTER-471012ab-0052: Criar, guardar, editar e submeter ofertas e procuras. | MASTER-e1169a77-0202: Criar, guardar, editar e submeter ofertas e procuras. | MASTER-f70ce307-0058: Criar, guardar, editar e submeter ofertas e procuras. | MASTER-51b8f0ad-0138: Criar, guardar, editar e submeter ofertas e procuras. | MASTER-edece80c-0202: Criar, guardar, editar e submeter ofertas e procuras.",P2,RF-MN-003: Relatório 25/09 conservado; estado histórico não promove conclusão | MASTER-471012ab-0052: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-e1169a77-0202: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-f70ce307-0058: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-51b8f0ad-0138: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-edece80c-0202: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas,RF-MN-003 | MASTER-471012ab-0052 | MASTER-e1169a77-0202 | MASTER-f70ce307-0058 | MASTER-51b8f0ad-0138 | MASTER-edece80c-0202,6,"[{""id"":""RF-MN-003"",""pedido"":""Criar, guardar, editar e submeter ofertas e procuras."",""fontes"":[""upgrade v4 SHA f0b9815…; p. 2965; requirements-audit.csv""],""aceitacao"":""Rascunho persiste; submissão bloqueia campos essenciais e gera número de protocolo."",""estado"":""parcial"",""variantes"":""Relatório 25/09 conservado; estado histórico não promove conclusão"",""modulo"":""governação/outros""},{""id"":""MASTER-471012ab-0052"",""pedido"":""Criar, guardar, editar e submeter ofertas e procuras."",""fontes"":[""portal do investidor\\upgrade do portal de investimentos.docx; SHA 471012abb0f56377956f4ab822fe3186e2561bee0471508a5d807091f2e0154f; w:p 581""],""aceitacao"":""Criar, guardar, editar e submeter ofertas e procuras."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""},{""id"":""MASTER-e1169a77-0202"",""pedido"":""Criar, guardar, editar e submeter ofertas e procuras."",""fontes"":[""portal do investidor\\Portal V4\\upgrade do portal de investimentos v6 programacao.docx; SHA e1169a7743b13ff6c950d3ffd4672a2afea32e81da84885e11154b7d8de9241e; w:p 3417""],""aceitacao"":""Criar, guardar, editar e submeter ofertas e procuras."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""},{""id"":""MASTER-f70ce307-0058"",""pedido"":""Criar, guardar, editar e submeter ofertas e procuras."",""fontes"":[""portal do investidor\\Portal V4\\upgrade do portal de investimentos v1.docx; SHA f70ce3075523c6278e00dce15b9c7acf2b690679e16959ae5f7d007e754cfc6e; w:p 588""],""aceitacao"":""Criar, guardar, editar e submeter ofertas e procuras."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""},{""id"":""MASTER-51b8f0ad-0138"",""pedido"":""Criar, guardar, editar e submeter ofertas e procuras."",""fontes"":[""portal do investidor\\Portal V4\\upgrade do portal de investimentos v2.docx; SHA 51b8f0ad3296d1064a5c12b450874fc72640869f10a164319cd7f3153dd3a086; w:p 2222""],""aceitacao"":""Criar, guardar, editar e submeter ofertas e procuras."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""},{""id"":""MASTER-edece80c-0202"",""pedido"":""Criar, guardar, editar e submeter ofertas e procuras."",""fontes"":[""portal do investidor\\Portal V4\\upgrade do portal de investimentos v5 programacao.docx; SHA edece80c0519537244f8a8b86456af0aa92de1c526f791bb0ee9b85af7a7e8b2; w:p 3417""],""aceitacao"":""Criar, guardar, editar e submeter ofertas e procuras."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""}]","QA - preparacao, revisao e historico privados; filtros e acesso pelo Dashboard existente; visual manual pendente",QA - operacoes autenticadas /minhas-oportunidades/ofertas e /{id}/revisao; MFA/ambito/conflitos verificados no servidor; publicacao recusada,"QA - cadastro Oportunidades, tarefas/auditoria/notificacoes nativos; snapshots e hashes por ciclo; sem tabelas ou migracoes novas","QA - recuperar oferta, correccao, decisao e versoes anteriores; conflitos, rollback e indisponibilidade sem downgrade | F4: guardar/recuperar particularidades; fotografias e provas no recurso existente; historico de aceitacao/autorizacao/retirada/revogacao por exemplar e versao, sem substituir dados ou decisoes. | F5: retirada sem perda de conteudo; revalidacao no mesmo identificador; historico de ciclos, expiracao, denuncias e planos administrativos com conflitos/etags.","QA - aprovacao interna privada distinta de publicacao; versao publica nativa anterior preservada | F3: projecao publica anterior preservada durante edicao/rejeicao da nova revisao; sem contacto, anexos ou coordenadas do negocio; moeda sem conversao. | F4: dados empresariais reservados so depois de NDA + autorizacao expressa do vendedor; comprovativos nunca entregues ao interessado. | F5: sem reexposicao de publicacao/grants antigos ao reactivar; exclusao uniforme em listas/filtros/mapas/API/CSV; validade opcional visivel quando definida; destaques administrativos privados.",Texto exatamente equivalente no mesmo módulo; sem fusão semântica aproximada,parcial,Requisito transversal; consultar estado-ativo-after.json,"Ofertas F1-F4 tecnicamente aceites com ressalvas nos commits 1f2990e4/91c18573/7691cab3/e431de8b. F5 desenvolvida em QA por autorizacao expressa de 14/11/2026, com retirada, revalidacao, validade configuravel, denuncias e preparacao administrativa. Publicacao efectiva e instalacao nao autorizadas.","Apreciacao da F5; NDA aprovado/exemplar/versao; tipos juridicos de comprovativos; prazo final de validade; avisos juridicos, visual/movel, GD/WebP e email real F2. Procuras nao abrangidas por este fecho de Ofertas.","Opportunity, PortalProfileAccess, OpportunityProfileWorkflow, OpportunityWorkflowTasks, documents/recursos, auditoria, Dashboard, filtros/mapa/API/CSV existentes; sem cadastro paralelo.",Documentos/decisoes concretos da Tempo e ferramenta visual; nao representam falta de autorizacao para este desenvolvimento QA. Instalacao/publicacao dependem de autorizacao expressa futura.,"Autorizacao F5 recebida: desenvolvimento exclusivo QA, sem publicacao, instalacao, pagamentos, dados operacionais ou mne. Prazo final e NDA nao presumidos; config por defeito nula.",Implementacao e verificacao QA de Ofertas; dependencias documentais/visuais e eventual entrega futura separadas.,Sem nova estimativa temporal nesta entrega; testes sobrepostos nao devem ser somados. Historico de estimativas conservado nas colunas anteriores.,docs/investmoz/v2/auditoria-portal/ofertas-fase5-fecho-qa-20261114.md; outputs/offers-private-phase5-20261114/qa-result.json; provas aceites F1-F4 referenciadas na matriz de aceitacao das cinco fases.,governação e demais cláusulas,14/11/2026 - fecho operacional de Ofertas em QA; visual/instalacao/publicacao com estados separados,,,Verificar individualmente este critério no contrato e percurso aplicável; a matriz inicial conserva texto/fonte sem o promover a aceitação.,"QA de UI quando browser disponível; reutilizar o domínio existente. Dados/decisões específicos no backlog, sem segunda validação de fonte oficial.",AGENTS.md — componentes do domínio encontrados; correspondência integral deste requisito não presumida,; suite global QA e GET por perfil disponíveis. Evidência do domínio não demonstra integralmente esta cláusula; UI individual pendente.,Não demonstrado individualmente; localizar critério e executar em QA.,,,,"0,5–2 h de análise/prova por cláusula sem correspondência, agrupada por contrato/consumidor; não somar estas faixas como projetos independentes. Implementação indeterminada até localizar lacuna.",,, RF-MN-004,"Aplicar campos condicionais por tipo de pessoa, modalidade e fase.",upgrade v4 SHA f0b9815…; p. 2969; requirements-audit.csv,governação/outros,/api/v1/investment/admin/interests/{interest}/status; /management/history; /management/interests; /management/interests/{interest}; /management/interests/{interest}/consent/contact; /management/interests/{interest}/consent/proponent; /management/opportunities; /management/opportunities/create; /management/opportunities/drafts; /management/opportunities/{id}/draft; /management/opportunities/{id}/edit; /management/opportunities/{id}/lifecycle,"Entidades, schema/configuração e serviços existentes; correspondência individual por estabelecer. Capacidade existente associada ao domínio; correspondência integral desta cláusula só quando demonstrada na prova individual.","Ativo investemoz:5432, 295 tabelas, COUNTRY02/030000/040000 instalados; seis categorias e administração habilitadas. Leitura atual não demonstra esta cláusula individual. Legado, percurso integrado, modulares/ofertas e notificações desabilitados.",outputs/investmoz-audit-current-20261012/runtime-active.json; runtime-qa18445.json. Inventário/GET do domínio não demonstra esta cláusula individual.,Sem evidência suficiente,Localizar obrigação verificável e consumidores/contrato atuais antes de estimar implementação ou declarar conclusão. Critério individual original preservado; cobertura atual ainda não individualizada.,"Ler secção/versão correspondente; distinguir requisito funcional, decisão e exemplo. Fontes oficiais já aceites. Nenhuma nova decisão identificada para reconciliar/demonstrar esta cláusula. Decisões específicas só após localizar a regra correspondente.","RF-MN-004: Campos aparecem e tornam-se obrigatórios conforme regras da matriz, sem perda de dados válidos.",P2,RF-MN-004: Relatório 25/09 conservado; estado histórico não promove conclusão,RF-MN-004,1,"[{""id"":""RF-MN-004"",""pedido"":""Aplicar campos condicionais por tipo de pessoa, modalidade e fase."",""fontes"":[""upgrade v4 SHA f0b9815…; p. 2969; requirements-audit.csv""],""aceitacao"":""Campos aparecem e tornam-se obrigatórios conforme regras da matriz, sem perda de dados válidos."",""estado"":""parcial"",""variantes"":""Relatório 25/09 conservado; estado histórico não promove conclusão"",""modulo"":""governação/outros""}]",Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Texto exatamente equivalente no mesmo módulo; sem fusão semântica aproximada,parcial,Requisito transversal; consultar estado-ativo-after.json,"A evidência existente é do domínio ou uma declaração histórica; não identifica a demonstração integral desta cláusula. Não afirmar implementado/parcial apenas por componente, menu ou teste global.",Localizar obrigação verificável e consumidores/contrato atuais antes de estimar implementação ou declarar conclusão. Critério individual original preservado; cobertura atual ainda não individualizada.,"Entidades, schema/configuração e serviços existentes; correspondência individual por estabelecer","Ler secção/versão correspondente; distinguir requisito funcional, decisão e exemplo. Fontes oficiais já aceites.",Nenhuma nova decisão identificada para reconciliar/demonstrar esta cláusula. Decisões específicas só após localizar a regra correspondente.,Reconciliação e prova individual antes de estimar alteração,Estimativa partilhada no lote correspondente de visao-consolidada-modulos.md; não somar por linha/cláusula/campo. Grupo: governação e demais cláusulas. Esforço de código adicional só após lacuna reproduzida.,outputs/investmoz-audit-current-20261012/runtime-active.json; runtime-qa18445.json. Inventário/GET do domínio não demonstra esta cláusula individual.,governação e demais cláusulas,12/10/2026 — reconciliação e HTTP; nova inspeção visual bloqueada,,,Verificar individualmente este critério no contrato e percurso aplicável; a matriz inicial conserva texto/fonte sem o promover a aceitação.,"QA de UI quando browser disponível; reutilizar o domínio existente. Dados/decisões específicos no backlog, sem segunda validação de fonte oficial.",AGENTS.md — componentes do domínio encontrados; correspondência integral deste requisito não presumida,; suite global QA e GET por perfil disponíveis. Evidência do domínio não demonstra integralmente esta cláusula; UI individual pendente.,Não demonstrado individualmente; localizar critério e executar em QA.,,,,"0,5–2 h de análise/prova por cláusula sem correspondência, agrupada por contrato/consumidor; não somar estas faixas como projetos independentes. Implementação indeterminada até localizar lacuna.",,, RF-MN-005,"Validar formatos, intervalos, dependências, duplicados e consentimentos.",upgrade v4 SHA f0b9815…; p. 2973; requirements-audit.csv | portal do investidor\upgrade do portal de investimentos.docx; SHA 471012abb0f56377956f4ab822fe3186e2561bee0471508a5d807091f2e0154f; w:p 589 | portal do investidor\Portal V4\upgrade do portal de investimentos v6 programacao.docx; SHA e1169a7743b13ff6c950d3ffd4672a2afea32e81da84885e11154b7d8de9241e; w:p 3425 | portal do investidor\Portal V4\upgrade do portal de investimentos v1.docx; SHA f70ce3075523c6278e00dce15b9c7acf2b690679e16959ae5f7d007e754cfc6e; w:p 596 | portal do investidor\Portal V4\upgrade do portal de investimentos v2.docx; SHA 51b8f0ad3296d1064a5c12b450874fc72640869f10a164319cd7f3153dd3a086; w:p 2230 | portal do investidor\Portal V4\upgrade do portal de investimentos v5 programacao.docx; SHA edece80c0519537244f8a8b86456af0aa92de1c526f791bb0ee9b85af7a7e8b2; w:p 3425,governação/outros,/api/v1/investment/admin/interests/{interest}/status; /management/history; /management/interests; /management/interests/{interest}; /management/interests/{interest}/consent/contact; /management/interests/{interest}/consent/proponent; /management/opportunities; /management/opportunities/create; /management/opportunities/drafts; /management/opportunities/{id}/draft; /management/opportunities/{id}/edit; /management/opportunities/{id}/lifecycle,"Entidades, schema/configuração e serviços existentes; correspondência individual por estabelecer. Capacidade existente associada ao domínio; correspondência integral desta cláusula só quando demonstrada na prova individual.","Ativo investemoz:5432, 295 tabelas, COUNTRY02/030000/040000 instalados; seis categorias e administração habilitadas. Leitura atual não demonstra esta cláusula individual. Legado, percurso integrado, modulares/ofertas e notificações desabilitados.",outputs/investmoz-audit-current-20261012/runtime-active.json; runtime-qa18445.json. Inventário/GET do domínio não demonstra esta cláusula individual.,Sem evidência suficiente,Localizar obrigação verificável e consumidores/contrato atuais antes de estimar implementação ou declarar conclusão. Critério individual original preservado; cobertura atual ainda não individualizada.,"Ler secção/versão correspondente; distinguir requisito funcional, decisão e exemplo. Fontes oficiais já aceites. Nenhuma nova decisão identificada para reconciliar/demonstrar esta cláusula. Decisões específicas só após localizar a regra correspondente.","RF-MN-005: O sistema impede submissão inválida e apresenta mensagem associada ao campo e orientação de correcção. | MASTER-471012ab-0054: Validar formatos, intervalos, dependências, duplicados e consentimentos. | MASTER-e1169a77-0204: Validar formatos, intervalos, dependências, duplicados e consentimentos. | MASTER-f70ce307-0060: Validar formatos, intervalos, dependências, duplicados e consentimentos. | MASTER-51b8f0ad-0140: Validar formatos, intervalos, dependências, duplicados e consentimentos. | MASTER-edece80c-0204: Validar formatos, intervalos, dependências, duplicados e consentimentos.",P2,RF-MN-005: Relatório 25/09 conservado; estado histórico não promove conclusão | MASTER-471012ab-0054: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-e1169a77-0204: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-f70ce307-0060: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-51b8f0ad-0140: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-edece80c-0204: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas,RF-MN-005 | MASTER-471012ab-0054 | MASTER-e1169a77-0204 | MASTER-f70ce307-0060 | MASTER-51b8f0ad-0140 | MASTER-edece80c-0204,6,"[{""id"":""RF-MN-005"",""pedido"":""Validar formatos, intervalos, dependências, duplicados e consentimentos."",""fontes"":[""upgrade v4 SHA f0b9815…; p. 2973; requirements-audit.csv""],""aceitacao"":""O sistema impede submissão inválida e apresenta mensagem associada ao campo e orientação de correcção."",""estado"":""parcial"",""variantes"":""Relatório 25/09 conservado; estado histórico não promove conclusão"",""modulo"":""governação/outros""},{""id"":""MASTER-471012ab-0054"",""pedido"":""Validar formatos, intervalos, dependências, duplicados e consentimentos."",""fontes"":[""portal do investidor\\upgrade do portal de investimentos.docx; SHA 471012abb0f56377956f4ab822fe3186e2561bee0471508a5d807091f2e0154f; w:p 589""],""aceitacao"":""Validar formatos, intervalos, dependências, duplicados e consentimentos."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""},{""id"":""MASTER-e1169a77-0204"",""pedido"":""Validar formatos, intervalos, dependências, duplicados e consentimentos."",""fontes"":[""portal do investidor\\Portal V4\\upgrade do portal de investimentos v6 programacao.docx; SHA e1169a7743b13ff6c950d3ffd4672a2afea32e81da84885e11154b7d8de9241e; w:p 3425""],""aceitacao"":""Validar formatos, intervalos, dependências, duplicados e consentimentos."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""},{""id"":""MASTER-f70ce307-0060"",""pedido"":""Validar formatos, intervalos, dependências, duplicados e consentimentos."",""fontes"":[""portal do investidor\\Portal V4\\upgrade do portal de investimentos v1.docx; SHA f70ce3075523c6278e00dce15b9c7acf2b690679e16959ae5f7d007e754cfc6e; w:p 596""],""aceitacao"":""Validar formatos, intervalos, dependências, duplicados e consentimentos."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""},{""id"":""MASTER-51b8f0ad-0140"",""pedido"":""Validar formatos, intervalos, dependências, duplicados e consentimentos."",""fontes"":[""portal do investidor\\Portal V4\\upgrade do portal de investimentos v2.docx; SHA 51b8f0ad3296d1064a5c12b450874fc72640869f10a164319cd7f3153dd3a086; w:p 2230""],""aceitacao"":""Validar formatos, intervalos, dependências, duplicados e consentimentos."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""},{""id"":""MASTER-edece80c-0204"",""pedido"":""Validar formatos, intervalos, dependências, duplicados e consentimentos."",""fontes"":[""portal do investidor\\Portal V4\\upgrade do portal de investimentos v5 programacao.docx; SHA edece80c0519537244f8a8b86456af0aa92de1c526f791bb0ee9b85af7a7e8b2; w:p 3425""],""aceitacao"":""Validar formatos, intervalos, dependências, duplicados e consentimentos."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""}]",Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Não demonstrado individualmente; localizar critério e executar em QA.,Texto exatamente equivalente no mesmo módulo; sem fusão semântica aproximada,parcial,Requisito transversal; consultar estado-ativo-after.json,"A evidência existente é do domínio ou uma declaração histórica; não identifica a demonstração integral desta cláusula. Não afirmar implementado/parcial apenas por componente, menu ou teste global. diff --git a/docs/investmoz/v2/auditoria-portal/ofertas-fase5-fecho-qa-20261114.md b/docs/investmoz/v2/auditoria-portal/ofertas-fase5-fecho-qa-20261114.md new file mode 100644 index 00000000..842500d7 --- /dev/null +++ b/docs/investmoz/v2/auditoria-portal/ofertas-fase5-fecho-qa-20261114.md @@ -0,0 +1,110 @@ +# Ofertas — Fase 5: fecho operacional em QA e aceitação das cinco fases + +14/11/2026. Autorização expressa da Tempo para desenvolvimento exclusivo em QA. Base aceite com ressalvas: `e431de8b` (Fase 4). Mantêm-se `7691cab3`, `91c18573`, `1f2990e4` e o plano `6e302db6`. Referência funcional: RF-MN-003 da matriz existente. + +**Fecho operacional implementado em QA. Publicação efectiva continua bloqueada. Nenhuma instalação em homologação/produção, alteração de dados operacionais, migração ou modificação em `mne`.** A aceitação desta entrega não é inferida da autorização para a desenvolver. + +## Disponibilidade, retirada e reactivação + +O dono registado da Oferta, com atribuição e representação ainda efectivas, pode retirar imediatamente a disponibilidade. A operação protectora não exige decisão editorial, MFA adicional do promotor ou uma versão de formulário actual: bloqueia o registo, reconfirma o dono e retira a versão corrente. Uma repetição não duplica a retirada. Não altera o conteúdo, apaga decisões ou converte o cadastro noutro produto. + +Disponibilidade e estado editorial são distintos. Uma aprovação privada pode continuar registada, mas a retirada exclui a Oferta das consultas, detalhe, filtros, mapa e CSV e fecha a leitura empresarial protegida pelo NDA. Uma consulta anterior já entregue ao utilizador não pode ser recolhida retroactivamente. + +Para voltar a disponibilizar, o proprietário pede nova validação no mesmo identificador. Abre-se um ciclo operacional que integra o hash do snapshot editorial (`private-offer-editorial-v4`); os contratos v2/v3 anteriores mantêm-se quando não há esse ciclo. O motor editorial existente conserva submissões, responsáveis, tarefas e decisões, reabre a preparação e exige submissão/revisão/aprovação independente. Só depois o proprietário pode repor a disponibilidade privada. Uma aprovação anterior ou um pedido de reactivação não bastam. + +Uma retirada durante revisão também pode abrir esse novo ciclo, fechando as tarefas anteriores pelo mecanismo existente. Um primeiro rascunho retirado continua a exigir a sua primeira validação. Preparações F1 antigas sem snapshot seguem primeiro a vinculação editorial já existente, sem fabricar um histórico de referências anterior. + +Reactivar disponibilidade **não publica**. O consumidor não reapresenta o snapshot público antigo: para um ciclo novo, exige a correspondência de `offer_cycle` e `editorial_hash` com o ciclo e a aprovação activados. Esses campos são o contrato de leitura futura, não um publicador. Nos testes, apenas projecções sintéticas são disponibilizadas por injecção interna na base isolada. + +## Validade independente do NDA + +`config/private_offers.php` acrescenta `validity_days` e `validity_policy_reference`, ambos inicialmente nulos. Não foi escolhido um prazo definitivo. Quando a Tempo definir um prazo, a configuração exige inteiro positivo e referência identificada; o limite técnico de 36.500 dias protege a aritmética e não é uma política comercial/jurídica. + +A política é congelada por hash na aprovação, e a contagem começa no instante UTC da publicação dessa versão. A aprovação privada não inicia a contagem. Alterar a configuração ou aprovar uma edição privada não prolonga a validade do snapshot público anterior; o exemplar legado sem `editorial_hash` conserva a primeira política congelada. Uma política pendente não é substituída por zero ou por um prazo presumido. + +No instante `agora >= publicação + duração`, a Oferta deixa de aparecer nas leituras. Essa exclusão não depende de cron, execução de um job, aceitação ou duração do NDA. A API acrescenta `expires_at` apenas quando a data é conhecida, e o cartão público mostra-a. O formato anterior do CSV foi preservado; os seus registos usam a mesma exclusão por disponibilidade/expiração. As respostas públicas de Ofertas usam `no-store` para não conservar uma versão retirada em caches HTTP. + +O comando `investmoz:offers-expire` confere por defeito **sem escrita**. Com `--confirm`, materializa uma expiração já efectiva e regista o evento uma vez, com actor de sistema nulo, sem criar utilizadores. O agendamento é optativo por `expiry_scheduler_enabled`, que permanece **false**. Não foi activado no ambiente operacional. Os ensaios do comando ocorreram exclusivamente na base de testes guardada. + +## Denúncias e Dashboard Colaborativo + +A Oferta dispõe de formulário de denúncia fundamentada. O utilizador deve estar autenticado, com email verificado, e poder consultar a versão pública ou já ter acesso privado legítimo ao registo. Um estranho não usa este percurso para descobrir um rascunho. A fundamentação é obrigatória; limites técnicos de texto e o throttle existente limitam os pedidos. + +Cada envio guarda identificador idempotente, autor, instante, versão do registo, hashes de referência e cópia cifrada do conteúdo referido. O fundamento também fica cifrado. Uma retoma com o mesmo actor/chave/conteúdo devolve o recibo sem criar outra tarefa, mesmo que entretanto a Oferta tenha sido retirada. Reutilizar a chave para outro conteúdo é conflito; outro actor não recebe o recibo. + +O mesmo `OpportunityWorkflowTasks` cria uma tarefa CRM restrita, sem inventar oportunidade CRM, responsável ou transporte. A entrada **Ofertas — acompanhamento**, dentro do Dashboard Colaborativo existente, abre a listagem privada existente em modo operacional, com filtro de denúncias pendentes. Não há outro dashboard nem alteração do menu principal. + +O validador interno com âmbito, MFA recente e sem conflito inicia a análise; fica associado à tarefa e à versão da denúncia. Conclui com fundamento, sem retirada ou com retirada da disponibilidade. Etag, versão da denúncia, estado e responsável são reconferidos no servidor sob bloqueio. O dono, auditor, outro âmbito ou outra conta não substituem essa autorização. A conclusão preserva os factos originais e o histórico; não é apresentada como decisão judicial ou certificação de fraude. + +O proprietário não recebe o fundamento original ou os dados do denunciante pela API de acompanhamento. A leitura desses dados exige o papel interno referido. A origem cifrada permite conferir a versão denunciada, mesmo que a preparação corrente seja alterada. Notificações, tarefas, pesquisa pública, mapa e CSV não recebem o texto da denúncia. + +## Preparação administrativa de destaques + +O Publicador existente, com âmbito, MFA, autorização efectiva e sem conflito, pode guardar uma preparação administrativa com designação, datas previstas, fundamento e estado **rascunho/arquivado**. O plano tem versão, etag, histórico e auditoria próprios no contexto da mesma Oferta. + +Não há estado comercial activo, alteração de `is_featured`, preço de destaque, cobrança, pagamento ou integração financeira. O proprietário e o público não recebem esse plano. A actualização de disponibilidade/denúncias preserva o plano local em edição; carregar o plano guardado exige uma acção explícita que identifica a substituição dos campos locais. + +## Segurança, confidencialidade e compatibilidade + +- Operações privadas reutilizam autenticação, perfis, representação, MFA, CSRF, limites de pedidos e respostas `no-store` existentes. As escritas usam transacções/bloqueio do registo; versões e etags impedem decisões sobre uma revisão desconhecida. A única dispensa de etag é a retirada protectora pelo dono, para não manter uma exposição por causa de um formulário antigo. +- A retirada/expiração fecha imediatamente a leitura protegida da empresa. Uma nova validação tem hash/ciclo novo; não reaproveita silenciosamente a aceitação/autorização NDA anterior. O interessado conserva a possibilidade de retirar uma aceitação antiga durante revisão. Não se apagaram os seus registos históricos. +- A Fase 4 mantém comprovativos apenas para validadores internos autorizados, originais cifrados, limite de seis fotografias, quarentena e análise de segurança. Não se acrescentou partilha externa de originais ou coordenadas exactas. +- A projecção pública continua separada da edição privada; a barreira normal continua fechada. APIs nativas dos outros produtos não passam a devolver Ofertas. Filtros, detalhe, mapas e CSV de Ofertas usam a mesma condição de disponibilidade e expiração. A propriedade opcional `expires_at` não altera as chaves dos exemplares antigos sem validade definida. +- Auditoria conserva actor, instante, versão, ciclo e identificadores de decisão. Fundamentos e origem das denúncias ficam cifrados; não há valores financeiros, contactos ou documentos nos títulos das tarefas ou nos eventos públicos. + +## Verificação + +Base: `offers_phase1_20261114_test`, loopback, porta **55441**, guardas existentes e dados exclusivamente sintéticos. Não foram utilizados processos reais, credenciais de negócio, publicação real ou entrega externa de email. O scanner sintético da Fase 4 mantém a identificação de teste e não foi registado no produto. + +Os resultados e hashes finais encontram-se em `outputs/offers-private-phase5-20261114/qa-result.json`. A passagem inicial focada teve **12 testes/151 asserções aprovados**. A regressão integrada terminou com **59 testes/780 asserções aprovados**, F1–F5, com os assets estabilizados. Os grupos sobrepostos não devem ser somados. + +| Grupo da passagem integrada | Testes | Asserções | Resultado | +| --- | ---: | ---: | --- | +| F1 — preparação privada | 14 | 146 | Aprovado | +| F2 — circuito editorial | 11 | 161 | Aprovado | +| F3 — consulta e filtros | 10 | 133 | Aprovado | +| F4 — particularidades/confidencialidade | 11 | 160 | Aprovado | +| F5 — fecho operacional | 13 | 180 | Aprovado | +| **Total desta passagem, sem somar a inicial** | **59** | **780** | **Aprovado** | + +Pint, PHPStan sem erros, TypeScript, lint/formatação dos oito ficheiros frontend e compilação QA passaram. A formatação global falhou nos mesmos 61 ficheiros preexistentes, fora do delta e com conteúdo Git inalterado. Não foram enfraquecidos testes, adicionadas excepções de análise ou instaladas dependências. A primeira passagem delimitada revelou uma recusa da antiga lista de acções à reabertura durante revisão: foi corrigida exclusivamente para o ciclo operacional pedido pelo dono, mantendo as regras dos restantes percursos. + +A prova cobre retirada com formulário antigo, repetição, actor/âmbito incorrectos, novo ciclo e nova validação, ausência de reexposição do snapshot antigo, primeira preparação, retirada durante revisão, preservação do histórico, fronteira exacta da expiração, política nula/configuração congelada, NDA anterior fechado, idempotência da denúncia, fonte cifrada, tarefa/filas nativas, tratamento e conflitos de versão, MFA expirado, auditor, destaque administrativo sem efeitos comerciais e HTML compilado das páginas afectadas. + +## Matriz de aceitação das cinco fases + +| Fase | Resultado demonstrado em QA | Referência aceite/prova | Instalação/publicação | Ressalvas e pendências | +| --- | --- | --- | --- | --- | +| 1 — Preparação privada | Criar, guardar, recuperar, editar e submeter; autorização/isolamento; cadastro de Oportunidades reutilizado | `1f2990e4`; `PrivateOfferPreparationTest` | Não instalada | Visual pendente | +| 2 — Circuito editorial | Dashboard existente, revisão, rejeição/correcção/reenvio, aprovação por versão, auditoria e notificações internas | `91c18573`, plano `6e302db6`; `PrivateOfferEditorialTest` | Não instalada; aprovação distinta de publicação | Visual e entrega real de email pendentes | +| 3 — Consulta e filtros | Projecção aprovada/publicada sintética, categorias/localização/preços sem câmbio, mapa administrativo, API/CSV e preservação da versão anterior | `7691cab3`; `OfferPublicCatalogueTest` e compatibilidade F5 | Publicação efectiva bloqueada | Visual/móvel; abertura real não autorizada; limite de representação de 1.000 candidatos/30 territórios preservado | +| 4 — Particularidades e confidencialidade | Campos confirmados, seis fotografias, comprovativos internos, duas condições NDA/vendedor, revogação/histórico e acessos por versão | `e431de8b`; `PrivateOfferConfidentialityTest` | Não instalada; NDA real inactivo | Exemplar/versão/aprovação do NDA; tipos jurídicos de comprovativo; GD/WebP; visual | +| 5 — Fecho operacional | Retirada imediata, revalidação/reativação, validade configurável/expiração, denúncias/tarefas/Dashboard, destaques administrativos e compatibilidade | Esta entrega; `PrivateOfferOperationsTest` e recibo QA | Sem instalação ou activação comercial/pública | Apreciação desta entrega; prazo/política final por definir; pendências das fases anteriores preservadas | + +Esta matriz integra o relatório solicitado e actualiza RF-MN-003 e o manual existentes; não é um novo inventário geral. O conjunto de Ofertas fica preparado em QA, mas não é declarado operacional em público, instalado, juridicamente fechado ou visualmente aceite. Procuras e o restante portal não são encerrados por esta entrega. + +## Ficheiros alterados + +| Grupo | Finalidade | +| --- | --- | +| `app/Services/OfferAvailability.php`, `PrivateOfferOperations.php` | Disponibilidade/validade, operações do dono, denúncias, tarefas, auditoria e planos administrativos | +| `OpportunityProfileWorkflow.php`, `PrivateOfferPreparation.php`, `PrivateOfferConfidentiality.php` | Ciclo operacional no snapshot, reabertura/revalidação existentes e exclusão da leitura NDA | +| `OfferPublicCatalogue.php`, `OfferPublicCatalogueController.php` | Exclusão consistente, contrato anterior preservado, validade opcional e cache HTTP | +| `PrivateOfferOperationsController.php`, `PrivateOfferPreparationController.php`, `PortalEditorialWorkflowController.php`, `routes/web.php` | Endpoints privados, filas no âmbito e entrada no Dashboard existente | +| `app/Console/Commands/ExpirePrivateOffers.php`, `routes/console.php`, `config/private_offers.php` | Conferência/materialização da expiração; prazo e agendamento sem activação por defeito | +| `resources/js/components/offer-operations.tsx`, `public-offer-card.tsx`, `lib/public-offer.ts` | Acompanhamento privado e data pública opcional | +| Páginas `offers/report`, `offers/preparation`, `offers/preparation-index`, `offers/public-detail`, `management/editorial-workflow` | Denúncia, retirada/reativação, tratamento e preparação administrativa integrados | +| `tests/Feature/PrivateOfferOperationsTest.php` | Testes novos de segurança, percursos e compatibilidade | +| Matriz RF-MN-003, manual, este relatório e recibo QA | Saldo e evidência das cinco fases | + +## Limitações e decisões pendentes + +1. **NDA:** a Tempo confirmou que ainda falta o exemplar/versão e a referência da aprovação. `private_offers.nda` mantém-se nulo. Não foi inventado texto jurídico ou uma assinatura com validade presumida. +2. **Imóveis:** os tipos jurídicos de comprovativo admissíveis continuam por identificar. Upload/scan/revisão interna não certificam titularidade, licenciamento ou disponibilidade do bem. +3. **Validade e divulgação:** a Tempo ainda não definiu prazo definitivo. A configuração permanece nula, sem presumir uma duração. Avisos jurídicos e eventuais condições comerciais de uma publicação futura não foram fabricados. +4. **Visual:** a conferência desktop/móvel das fases anteriores permanece pendente. A última tentativa registada do conector devolveu `The browser access host is unavailable`. Não foram repetidos reinícios, desactivados controlos ou apresentados HTML/compilação como aceitação visual. +5. **Imagens:** GD/WebP não está disponível neste PHP QA. Os originais privados e as validações da F4 foram demonstrados; geração/inspecção de derivadas e galeria pública não são declaradas concluídas. Não foram instaladas extensões. +6. **Email:** a entrega real da F2 continua pendente. Não foram activados novos transportes, SMTP, SMS ou destinatários operacionais. +7. **Qualidade preexistente:** a formatação global mantém os mesmos 61 ficheiros fora do delta. Lint focado, tipos e análise estática são registados separadamente dessa falha anterior. + +Próximo passo: apreciação desta entrega QA e resolução das dependências documentais/visuais indicadas. **Qualquer instalação, configuração activa ou publicação aguarda autorização expressa da Tempo.** diff --git a/outputs/offers-private-phase5-20261114/build-final.log b/outputs/offers-private-phase5-20261114/build-final.log new file mode 100644 index 00000000..fe258ce0 --- /dev/null +++ b/outputs/offers-private-phase5-20261114/build-final.log @@ -0,0 +1,332 @@ + +> build +> vp build + +npm.cmd : [plugin laravel:fonts] Optimized font fallbacks require the optional "fontaine" package. Install it, or set +"optimizedFallbacks: false" on your fonts to disable the feature. +At line:2 char:30 ++ ... _BUILD='1'; npm.cmd run build *> outputs/offers-private-phase5-202611 ... ++ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + CategoryInfo : NotSpecified: ([plugin laravel...le the feature.:String) [], RemoteException + + FullyQualifiedErrorId : NativeCommandError + +transforming... +✓ 2847 modules transformed. +rendering chunks... +computing gzip size... +public/build-qa-opportunities/assets/layers-BWBAp2CZ.png 0.69 kB +public/build-qa-opportunities/assets/layers-2x-Bpkbi35X.png 1.25 kB +public/build-qa-opportunities/assets/marker-icon-hN30_KVU.png 1.46 kB +public/build-qa-opportunities/assets/noto-sans-400-normal-BTkUljjl.woff2 13.12 kB +public/build-qa-opportunities/assets/noto-sans-700-normal-CrvTEWhy.woff2 13.46 kB +public/build-qa-opportunities/assets/noto-sans-500-normal-DCA2Z9Cj.woff2 13.49 kB +public/build-qa-opportunities/assets/noto-sans-600-normal-Dkgu5AE5.woff2 13.49 kB +public/build-qa-opportunities/assets/noto-serif-400-normal-BV_iYGc9.woff2 14.40 kB +public/build-qa-opportunities/assets/noto-serif-700-normal-CIw17UNa.woff2 15.23 kB +public/build-qa-opportunities/assets/noto-serif-600-normal-BiuDA9L7.woff2 15.31 kB +public/build-qa-opportunities/assets/noto-sans-400-normal-DPpKfCgK.woff 16.68 kB +public/build-qa-opportunities/assets/instrument-sans-400-normal-DRC__1Mx.woff2 16.86 kB +public/build-qa-opportunities/assets/noto-sans-700-normal-ZyrKABVF.woff 16.95 kB +public/build-qa-opportunities/assets/noto-sans-500-normal-CDPngY5C.woff 17.09 kB +public/build-qa-opportunities/assets/noto-sans-600-normal-DVJMj_t0.woff 17.10 kB +public/build-qa-opportunities/assets/instrument-sans-500-normal-Dk9ku72i.woff2 17.23 kB +public/build-qa-opportunities/assets/instrument-sans-600-normal-B7fBEWYG.woff2 17.40 kB +public/build-qa-opportunities/assets/noto-serif-400-normal-DT0cwSa_.woff 17.81 kB +public/build-qa-opportunities/fonts-manifest.json 18.53 kB │ gzip: 1.23 kB +public/build-qa-opportunities/assets/noto-serif-700-normal-mX-AlFMT.woff 18.54 kB +public/build-qa-opportunities/assets/noto-serif-600-normal-RZopCp-J.woff 18.68 kB +public/build-qa-opportunities/assets/instrument-sans-400-normal-D1W7dsQl.woff 21.24 kB +public/build-qa-opportunities/assets/instrument-sans-500-normal-Z6ESRlEs.woff 21.65 kB +public/build-qa-opportunities/assets/instrument-sans-600-normal-B9e8oLYv.woff 21.67 kB +public/build-qa-opportunities/manifest.json 114.60 kB │ gzip: 11.96 kB +public/build-qa-opportunities/assets/fonts-CcFLQaQ-.css 7.91 kB │ gzip: 0.65 kB +public/build-qa-opportunities/assets/investment-catalogue-WaRR19FV.css 8.15 kB │ gzip: 2.00 kB +public/build-qa-opportunities/assets/interactive-investment-map-Bm3WjoQm.css 10.62 kB │ gzip: 2.65 kB +public/build-qa-opportunities/assets/app-UznX-FqS.css 157.42 kB │ gzip: 25.28 kB +public/build-qa-opportunities/assets/input-text-BlEFHnL-.js 0.09 kB │ gzip: 0.10 kB +public/build-qa-opportunities/assets/check-PcBNn_NG.js 0.12 kB │ gzip: 0.14 kB +public/build-qa-opportunities/assets/company-comparison-qLTgpXjz.js 0.12 kB │ gzip: 0.13 kB +public/build-qa-opportunities/assets/display-value-DRR1EAAK.js 0.13 kB │ gzip: 0.12 kB +public/build-qa-opportunities/assets/value-text-BztaYdGl.js 0.14 kB │ gzip: 0.12 kB +public/build-qa-opportunities/assets/loader-circle-DS8Tf4er.js 0.14 kB │ gzip: 0.15 kB +public/build-qa-opportunities/assets/shared-B-QpuOU2.js 0.15 kB │ gzip: 0.13 kB +public/build-qa-opportunities/assets/arrow-left-DMVHIw8Z.js 0.16 kB │ gzip: 0.16 kB +public/build-qa-opportunities/assets/search-BRvPeo47.js 0.17 kB │ gzip: 0.16 kB +public/build-qa-opportunities/assets/circle-check-DckzuBoW.js 0.17 kB │ gzip: 0.16 kB +public/build-qa-opportunities/assets/product-usage-CHhCL93_.js 0.18 kB │ gzip: 0.17 kB +public/build-qa-opportunities/assets/editorial-states-Bc7NQzsj.js 0.19 kB │ gzip: 0.16 kB +public/build-qa-opportunities/assets/types-BoD_IJFv.js 0.19 kB │ gzip: 0.16 kB +public/build-qa-opportunities/assets/participation-identity-BWabb4yN.js 0.24 kB │ gzip: 0.18 kB +public/build-qa-opportunities/assets/circle-alert-uV7omYjr.js 0.24 kB │ gzip: 0.18 kB +public/build-qa-opportunities/assets/eye-CmcEBfxN.js 0.25 kB │ gzip: 0.20 kB +public/build-qa-opportunities/assets/log-in-Wgc8VLfq.js 0.25 kB │ gzip: 0.22 kB +public/build-qa-opportunities/assets/partnership-history-CfclSLAo.js 0.27 kB │ gzip: 0.19 kB +public/build-qa-opportunities/assets/compiler-runtime-sKsOXjDo.js 0.27 kB │ gzip: 0.23 kB +public/build-qa-opportunities/assets/file-text-CEJtuNfp.js 0.33 kB │ gzip: 0.21 kB +public/build-qa-opportunities/assets/confirm-Culw9Q-x.js 0.36 kB │ gzip: 0.22 kB +public/build-qa-opportunities/assets/text-link-BBvcP60s.js 0.40 kB │ gzip: 0.27 kB +public/build-qa-opportunities/assets/content-management-entry-C_oY4VyW.js 0.42 kB │ gzip: 0.31 kB +public/build-qa-opportunities/assets/handshake-YqyTTm3a.js 0.44 kB │ gzip: 0.28 kB +public/build-qa-opportunities/assets/offer-editorial-BOYEEQS3.js 0.45 kB │ gzip: 0.27 kB +public/build-qa-opportunities/assets/investment-guides-DO58L8zr.js 0.45 kB │ gzip: 0.26 kB +public/build-qa-opportunities/assets/catalogue-context-epuehaJf.js 0.47 kB │ gzip: 0.30 kB +public/build-qa-opportunities/assets/operation-key-Dr4VtGxa.js 0.51 kB │ gzip: 0.35 kB +public/build-qa-opportunities/assets/spinner-Dmx1j41V.js 0.52 kB │ gzip: 0.36 kB +public/build-qa-opportunities/assets/types-Cxp_Nskt.js 0.55 kB │ gzip: 0.32 kB +public/build-qa-opportunities/assets/sliders-horizontal-D-4FqAHA.js 0.58 kB │ gzip: 0.26 kB +public/build-qa-opportunities/assets/page-preparation-guard-B-OckhBt.js 0.59 kB │ gzip: 0.35 kB +public/build-qa-opportunities/assets/portal-presentation-fH7FShU8.js 0.62 kB │ gzip: 0.38 kB +public/build-qa-opportunities/assets/confidentiality-C2eqbRBb.js 0.64 kB │ gzip: 0.41 kB +public/build-qa-opportunities/assets/dist-qw3FPjRO.js 0.74 kB │ gzip: 0.49 kB +public/build-qa-opportunities/assets/labels-DxqjesIG.js 0.74 kB │ gzip: 0.46 kB +public/build-qa-opportunities/assets/wayfinder-Dp7PqxvV.js 0.89 kB │ gzip: 0.53 kB +public/build-qa-opportunities/assets/createLucideIcon-oMeAlXbs.js 0.95 kB │ gzip: 0.56 kB +public/build-qa-opportunities/assets/use-interest-draft-waHUBAew.js 0.99 kB │ gzip: 0.51 kB +public/build-qa-opportunities/assets/context-documents-Bh7vrWBo.js 1.01 kB │ gzip: 0.57 kB +public/build-qa-opportunities/assets/label-Dpfg_IeY.js 1.02 kB │ gzip: 0.62 kB +public/build-qa-opportunities/assets/technical-incident-labels-DlL6bSeE.js 1.02 kB │ gzip: 0.51 kB +public/build-qa-opportunities/assets/investment-consumer-identity-Cxp_hGfX.js 1.07 kB │ gzip: 0.59 kB +public/build-qa-opportunities/assets/article-C5PlVvrU.js 1.08 kB │ gzip: 0.65 kB +public/build-qa-opportunities/assets/search-state-RHLy6yI7.js 1.10 kB │ gzip: 0.68 kB +public/build-qa-opportunities/assets/rolldown-runtime-Dd_uD5pT.js 1.10 kB │ gzip: 0.62 kB +public/build-qa-opportunities/assets/catalogue-investment-map-BMbmCrqR.js 1.12 kB │ gzip: 0.62 kB +public/build-qa-opportunities/assets/investment-contexts-BQGiwu9n.js 1.17 kB │ gzip: 0.60 kB +public/build-qa-opportunities/assets/profile-json-Bza-VQzw.js 1.20 kB │ gzip: 0.72 kB +public/build-qa-opportunities/assets/document-T3QM1nf4.js 1.23 kB │ gzip: 0.72 kB +public/build-qa-opportunities/assets/shared-JTxA6RNb.js 1.24 kB │ gzip: 0.74 kB +public/build-qa-opportunities/assets/public-detail-TGFEaj8I.js 1.25 kB │ gzip: 0.72 kB +public/build-qa-opportunities/assets/demographic-context-BMdtlxjJ.js 1.27 kB │ gzip: 0.73 kB +public/build-qa-opportunities/assets/public-offer-card-Dhz4h4ig.js 1.30 kB │ gzip: 0.70 kB +public/build-qa-opportunities/assets/input-SCOe2C97.js 1.36 kB │ gzip: 0.75 kB +public/build-qa-opportunities/assets/cluster-competitiveness-hNV0FaaW.js 1.37 kB │ gzip: 0.73 kB +public/build-qa-opportunities/assets/verify-email-CBwdNKm0.js 1.40 kB │ gzip: 0.74 kB +public/build-qa-opportunities/assets/business-types-SGCXhH6X.js 1.47 kB │ gzip: 0.75 kB +public/build-qa-opportunities/assets/verification-BOcn3HIw.js 1.48 kB │ gzip: 0.52 kB +public/build-qa-opportunities/assets/country-select-4uP5BvMf.js 1.49 kB │ gzip: 0.78 kB +public/build-qa-opportunities/assets/territorial-source-evidence-eKB4ZB36.js 1.53 kB │ gzip: 0.83 kB +public/build-qa-opportunities/assets/manage-DNCBnmgf.js 1.56 kB │ gzip: 0.74 kB +public/build-qa-opportunities/assets/educational-article-card-0rCqZ1kX.js 1.59 kB │ gzip: 0.72 kB +public/build-qa-opportunities/assets/partnership-source-links-VfrXk2FZ.js 1.65 kB │ gzip: 0.91 kB +public/build-qa-opportunities/assets/catalogue-vIlL0AoR.js 1.78 kB │ gzip: 0.96 kB +public/build-qa-opportunities/assets/technical-incident-notice-CjziWA6m.js 1.79 kB │ gzip: 0.89 kB +public/build-qa-opportunities/assets/report-DRxtlCFu.js 1.80 kB │ gzip: 0.93 kB +public/build-qa-opportunities/assets/opportunity-technical-incidents-C4aVbut7.js 1.88 kB │ gzip: 0.98 kB +public/build-qa-opportunities/assets/password-input-BCuBEWkB.js 1.88 kB │ gzip: 1.05 kB +public/build-qa-opportunities/assets/passkey-verify-D57KR0ve.js 2.04 kB │ gzip: 1.15 kB +public/build-qa-opportunities/assets/public-record-offline-BmjvnSLE.js 2.07 kB │ gzip: 1.22 kB +public/build-qa-opportunities/assets/investors-Di6y94fY.js 2.09 kB │ gzip: 1.15 kB +public/build-qa-opportunities/assets/forgot-password-T9DcZJvN.js 2.10 kB │ gzip: 0.98 kB +public/build-qa-opportunities/assets/sources-C4rNpAhk.js 2.13 kB │ gzip: 1.12 kB +public/build-qa-opportunities/assets/saved-CuZHxZQR.js 2.21 kB │ gzip: 1.10 kB +public/build-qa-opportunities/assets/zone-map-SY-hKDZP.js 2.26 kB │ gzip: 1.16 kB +public/build-qa-opportunities/assets/appearance-Drnhw1ee.js 2.36 kB │ gzip: 1.14 kB +public/build-qa-opportunities/assets/shared-CBJ3c_x-.js 2.37 kB │ gzip: 1.24 kB +public/build-qa-opportunities/assets/editorial-subjects-C6_MfRSK.js 2.38 kB │ gzip: 1.14 kB +public/build-qa-opportunities/assets/context-installation-ixvPSLFr.js 2.39 kB │ gzip: 1.23 kB +public/build-qa-opportunities/assets/workspace-4_R1CLYI.js 2.40 kB │ gzip: 1.20 kB +public/build-qa-opportunities/assets/reset-password-Dm0VnxgR.js 2.43 kB │ gzip: 0.98 kB +public/build-qa-opportunities/assets/public-document-content-BHvs9J7S.js 2.47 kB │ gzip: 0.80 kB +public/build-qa-opportunities/assets/manage-Cdsvzg2y.js 2.47 kB │ gzip: 1.10 kB +public/build-qa-opportunities/assets/confirm-password-BYmSXd2u.js 2.47 kB │ gzip: 1.06 kB +public/build-qa-opportunities/assets/structures-Dr2nXdub.js 2.49 kB │ gzip: 1.35 kB +public/build-qa-opportunities/assets/preparation-index-DEe7dOdy.js 2.53 kB │ gzip: 1.07 kB +public/build-qa-opportunities/assets/bootstrap-confirmation-9RmA5-Yz.js 2.55 kB │ gzip: 1.19 kB +public/build-qa-opportunities/assets/territory-associations-D-P2rzJv.js 2.55 kB │ gzip: 1.11 kB +public/build-qa-opportunities/assets/installation-Dly8Xg6e.js 2.59 kB │ gzip: 1.28 kB +public/build-qa-opportunities/assets/technical-support-DlW3Jv7q.js 2.60 kB │ gzip: 1.18 kB +public/build-qa-opportunities/assets/management-CvdeRZYq.js 2.68 kB │ gzip: 1.18 kB +public/build-qa-opportunities/assets/experiments-_6FE66dI.js 2.73 kB │ gzip: 1.31 kB +public/build-qa-opportunities/assets/password-CB_NxgQN.js 2.73 kB │ gzip: 0.73 kB +public/build-qa-opportunities/assets/opportunity-install-B232n6lM.js 2.75 kB │ gzip: 1.34 kB +public/build-qa-opportunities/assets/dist-BobFCjoG.js 2.92 kB │ gzip: 1.29 kB +public/build-qa-opportunities/assets/portal-api-CpQBRfcu.js 2.98 kB │ gzip: 1.50 kB +public/build-qa-opportunities/assets/two-factor-challenge-C-VleRaI.js 3.24 kB │ gzip: 1.52 kB +public/build-qa-opportunities/assets/territory-geometry-preview-952xh2Er.js 3.25 kB │ gzip: 1.47 kB +public/build-qa-opportunities/assets/workspace-BiFp0p_Q.js 3.29 kB │ gzip: 1.44 kB +public/build-qa-opportunities/assets/partnership-agreement-instrument-CCnuZNBy.js 3.43 kB │ gzip: 1.49 kB +public/build-qa-opportunities/assets/funding-classification-CXvhHOwp.js 3.44 kB │ gzip: 1.64 kB +public/build-qa-opportunities/assets/guide-mndEuKFT.js 3.45 kB │ gzip: 1.43 kB +public/build-qa-opportunities/assets/history-DSPDM8gM.js 3.46 kB │ gzip: 1.70 kB +public/build-qa-opportunities/assets/audit-Xdo8qR4z.js 3.51 kB │ gzip: 1.69 kB +public/build-qa-opportunities/assets/show-D2EX4aTO.js 3.60 kB │ gzip: 1.82 kB +public/build-qa-opportunities/assets/demography-BaQAqCAU.js 3.70 kB │ gzip: 1.79 kB +public/build-qa-opportunities/assets/corridor-agribusiness-BcIVf-hW.js 3.72 kB │ gzip: 1.53 kB +public/build-qa-opportunities/assets/business-opportunity-5M_31Cua.js 3.73 kB │ gzip: 1.63 kB +public/build-qa-opportunities/assets/management-access-D4VE9LUB.js 3.75 kB │ gzip: 1.43 kB +public/build-qa-opportunities/assets/businesses-A6nx0k6S.js 3.76 kB │ gzip: 1.57 kB +public/build-qa-opportunities/assets/editorial-reading-DUFQmJtD.js 3.89 kB │ gzip: 1.68 kB +public/build-qa-opportunities/assets/catalog-B6dpdVsW.js 3.95 kB │ gzip: 1.51 kB +public/build-qa-opportunities/assets/desk-assignments-CfWpiIpK.js 3.99 kB │ gzip: 1.95 kB +public/build-qa-opportunities/assets/dashboard-CxoTLkSg.js 3.99 kB │ gzip: 1.61 kB +public/build-qa-opportunities/assets/investment-context-cards-gJe_oyxw.js 4.05 kB │ gzip: 1.84 kB +public/build-qa-opportunities/assets/shared-7qc3eq3D.js 4.09 kB │ gzip: 2.05 kB +public/build-qa-opportunities/assets/opportunity-contact-form-Cb6iDQ1U.js 4.10 kB │ gzip: 1.67 kB +public/build-qa-opportunities/assets/demographic-evidence-DGwOnf5g.js 4.38 kB │ gzip: 1.84 kB +public/build-qa-opportunities/assets/conversation-Dkxk4yT2.js 4.42 kB │ gzip: 1.95 kB +public/build-qa-opportunities/assets/portfolio-Dmt3KDgL.js 4.45 kB │ gzip: 1.74 kB +public/build-qa-opportunities/assets/reference-C9cU7M38.js 4.46 kB │ gzip: 2.00 kB +public/build-qa-opportunities/assets/territory-geometry-import-BnSl5jzG.js 4.49 kB │ gzip: 1.90 kB +public/build-qa-opportunities/assets/opportunity-origin-D3o6OTRg.js 4.64 kB │ gzip: 2.07 kB +public/build-qa-opportunities/assets/context-inheritance-dd0VgP8h.js 4.64 kB │ gzip: 2.00 kB +public/build-qa-opportunities/assets/installation-CnslW5oi.js 4.67 kB │ gzip: 1.95 kB +public/build-qa-opportunities/assets/opportunities-YsMGMCR_.js 4.73 kB │ gzip: 1.77 kB +public/build-qa-opportunities/assets/references-Cu0zjt4N.js 4.79 kB │ gzip: 2.22 kB +public/build-qa-opportunities/assets/offer-confidential-access-Bu9EgZ3d.js 4.83 kB │ gzip: 1.91 kB +public/build-qa-opportunities/assets/opportunity-curation-7Gt2cWer.js 4.88 kB │ gzip: 2.04 kB +public/build-qa-opportunities/assets/business-verifications-D6fOYDwf.js 4.90 kB │ gzip: 1.71 kB +public/build-qa-opportunities/assets/crm-edit-comparison-Db53T7I7.js 4.91 kB │ gzip: 1.99 kB +public/build-qa-opportunities/assets/investment-complements-CXfMOoGq.js 5.00 kB │ gzip: 2.05 kB +public/build-qa-opportunities/assets/agreement-history-BfSa8E7Q.js 5.13 kB │ gzip: 2.10 kB +public/build-qa-opportunities/assets/catalogue-D8gbcfdu.js 5.13 kB │ gzip: 2.54 kB +public/build-qa-opportunities/assets/workspace-BeyUwWtD.js 5.20 kB │ gzip: 2.18 kB +public/build-qa-opportunities/assets/article-editor-ByLxbPbJ.js 5.25 kB │ gzip: 2.20 kB +public/build-qa-opportunities/assets/context-start-B2aU0zYo.js 5.46 kB │ gzip: 2.10 kB +public/build-qa-opportunities/assets/economic-references-C7dNVUC6.js 5.61 kB │ gzip: 2.52 kB +public/build-qa-opportunities/assets/convert-B3LtI2n6.js 5.81 kB │ gzip: 2.40 kB +public/build-qa-opportunities/assets/editorial-subject-form-B2W8E2NJ.js 5.87 kB │ gzip: 2.66 kB +public/build-qa-opportunities/assets/public-sector-cards-Esb1aTm6.js 5.94 kB │ gzip: 2.55 kB +public/build-qa-opportunities/assets/demography-install-B57rikBv.js 6.05 kB │ gzip: 2.50 kB +public/build-qa-opportunities/assets/context-C_aFYdX-.js 6.06 kB │ gzip: 2.65 kB +public/build-qa-opportunities/assets/request-YZw1-_QG.js 6.10 kB │ gzip: 2.41 kB +public/build-qa-opportunities/assets/structure-geography-ELCgr0dd.js 6.12 kB │ gzip: 2.22 kB +public/build-qa-opportunities/assets/editorial-workflow-DYYZzXOw.js 6.22 kB │ gzip: 2.29 kB +public/build-qa-opportunities/assets/investment-map-panel-BgicrnXE.js 6.27 kB │ gzip: 2.88 kB +public/build-qa-opportunities/assets/procurement-HZSwnYGC.js 6.35 kB │ gzip: 2.73 kB +public/build-qa-opportunities/assets/register-vy51_8_H.js 6.39 kB │ gzip: 2.70 kB +public/build-qa-opportunities/assets/funding-Dp_3_Uu2.js 6.49 kB │ gzip: 2.00 kB +public/build-qa-opportunities/assets/leads-Bu2UK8ON.js 6.56 kB │ gzip: 2.82 kB +public/build-qa-opportunities/assets/business-marketplace-C4h1dExR.js 6.59 kB │ gzip: 2.51 kB +public/build-qa-opportunities/assets/interest-xqtiIFPC.js 6.63 kB │ gzip: 2.23 kB +public/build-qa-opportunities/assets/demography-BxaBdOBx.js 6.66 kB │ gzip: 2.51 kB +public/build-qa-opportunities/assets/fiscal-qa-review-CEzw2OQP.js 6.75 kB │ gzip: 2.70 kB +public/build-qa-opportunities/assets/business-interest-B01b-tRY.js 6.82 kB │ gzip: 2.60 kB +public/build-qa-opportunities/assets/interests-BcpPowrJ.js 6.83 kB │ gzip: 2.78 kB +public/build-qa-opportunities/assets/catalogue-CoNk3Fdv.js 6.84 kB │ gzip: 2.81 kB +public/build-qa-opportunities/assets/company-comparison-8kehiDPm.js 6.92 kB │ gzip: 2.60 kB +public/build-qa-opportunities/assets/business-classification-C8hVj93v.js 7.01 kB │ gzip: 2.57 kB +public/build-qa-opportunities/assets/dist-Ddn_hEUd.js 7.02 kB │ gzip: 2.71 kB +public/build-qa-opportunities/assets/blank-2jFZEe2q.js 7.07 kB │ gzip: 2.77 kB +public/build-qa-opportunities/assets/analysis-fj0joeC4.js 7.11 kB │ gzip: 2.96 kB +public/build-qa-opportunities/assets/profile-CidlqkJE.js 7.19 kB │ gzip: 2.47 kB +public/build-qa-opportunities/assets/install-BUqVTelf.js 7.23 kB │ gzip: 3.03 kB +public/build-qa-opportunities/assets/investor-COYuuRY_.js 7.30 kB │ gzip: 2.89 kB +public/build-qa-opportunities/assets/investor-path-CSExMjAt.js 7.36 kB │ gzip: 3.02 kB +public/build-qa-opportunities/assets/proactive-supply-review-Bspf5LP7.js 7.55 kB │ gzip: 2.73 kB +public/build-qa-opportunities/assets/legacy-Cgz97_0u.js 7.67 kB │ gzip: 2.56 kB +public/build-qa-opportunities/assets/jsx-runtime-BpzPEenQ.js 7.89 kB │ gzip: 2.99 kB +public/build-qa-opportunities/assets/shared-BFCbvJo7.js 7.90 kB │ gzip: 3.08 kB +public/build-qa-opportunities/assets/business-objects-Uo9gKzR4.js 7.95 kB │ gzip: 2.57 kB +public/build-qa-opportunities/assets/assisted-tUytMJpX.js 8.06 kB │ gzip: 3.16 kB +public/build-qa-opportunities/assets/value-network-Cp8UYtMs.js 8.06 kB │ gzip: 3.08 kB +public/build-qa-opportunities/assets/editor-BpHntkYH.js 8.21 kB │ gzip: 2.97 kB +public/build-qa-opportunities/assets/territorial-editorial-B0ni24WA.js 8.22 kB │ gzip: 3.03 kB +public/build-qa-opportunities/assets/opportunity-promotion-CyWpImc1.js 8.40 kB │ gzip: 2.96 kB +public/build-qa-opportunities/assets/publication-history-DfLO6jOc.js 8.42 kB │ gzip: 3.02 kB +public/build-qa-opportunities/assets/print-Tn3aXHlY.js 8.66 kB │ gzip: 3.55 kB +public/build-qa-opportunities/assets/dialog-C4ibpf_J.js 8.81 kB │ gzip: 2.77 kB +public/build-qa-opportunities/assets/shared-DXIXdF9G.js 8.92 kB │ gzip: 3.83 kB +public/build-qa-opportunities/assets/iva-modelo-a-CNylSdBc.js 8.94 kB │ gzip: 3.27 kB +public/build-qa-opportunities/assets/zone-relations-BLf96C6y.js 8.95 kB │ gzip: 2.99 kB +public/build-qa-opportunities/assets/investment-territory-MNjWS_u0.js 8.96 kB │ gzip: 3.43 kB +public/build-qa-opportunities/assets/similar-businesses-HTwQGVGQ.js 9.11 kB │ gzip: 3.58 kB +public/build-qa-opportunities/assets/interactive-investment-map-BClK9yWO.js 9.13 kB │ gzip: 3.60 kB +public/build-qa-opportunities/assets/investment-land-preparation-BHeC9B2U.js 9.15 kB │ gzip: 3.33 kB +public/build-qa-opportunities/assets/why-invest-Cwz2gK0p.js 9.16 kB │ gzip: 3.49 kB +public/build-qa-opportunities/assets/interest-history-oHNykKmC.js 9.48 kB │ gzip: 3.45 kB +public/build-qa-opportunities/assets/content-hub-CzsGgtNo.js 9.95 kB │ gzip: 2.93 kB +public/build-qa-opportunities/assets/privacy-policy-LI1lpUCK.js 10.36 kB │ gzip: 3.07 kB +public/build-qa-opportunities/assets/editorial-edition-Zq39X7mf.js 10.36 kB │ gzip: 3.47 kB +public/build-qa-opportunities/assets/panel-D5bg3ajo.js 10.46 kB │ gzip: 4.29 kB +public/build-qa-opportunities/assets/result-yez5wOFo.js 10.57 kB │ gzip: 4.73 kB +public/build-qa-opportunities/assets/dashboard-C0NQF0VJ.js 10.92 kB │ gzip: 3.47 kB +public/build-qa-opportunities/assets/zone-attachments-CLqpntOa.js 10.94 kB │ gzip: 2.75 kB +public/build-qa-opportunities/assets/business-D-GsDwzk.js 11.10 kB │ gzip: 3.09 kB +public/build-qa-opportunities/assets/detail-3fwgDkH3.js 11.73 kB │ gzip: 4.30 kB +public/build-qa-opportunities/assets/login-CHsqdM2X.js 11.82 kB │ gzip: 4.90 kB +public/build-qa-opportunities/assets/support-installation-tsBpnQkP.js 12.36 kB │ gzip: 4.86 kB +public/build-qa-opportunities/assets/show-HFfMuLZ-.js 12.37 kB │ gzip: 3.98 kB +public/build-qa-opportunities/assets/show-D9Qj4eSh.js 12.41 kB │ gzip: 4.39 kB +public/build-qa-opportunities/assets/guide-eWGplRw4.js 12.58 kB │ gzip: 4.03 kB +public/build-qa-opportunities/assets/contact-CrhBB858.js 12.65 kB │ gzip: 4.33 kB +public/build-qa-opportunities/assets/project-dossier-document-CiCplPXo.js 12.68 kB │ gzip: 5.20 kB +public/build-qa-opportunities/assets/dossier-prefill-B90PO41l.js 12.72 kB │ gzip: 4.65 kB +public/build-qa-opportunities/assets/technical-incident-GDB_x0wp.js 12.73 kB │ gzip: 4.96 kB +public/build-qa-opportunities/assets/competitiveness-60nqrOfg.js 12.74 kB │ gzip: 4.23 kB +public/build-qa-opportunities/assets/editor-t_I3E9vl.js 12.91 kB │ gzip: 3.75 kB +public/build-qa-opportunities/assets/fiscal-forms-CCePiKSA.js 13.04 kB │ gzip: 4.75 kB +public/build-qa-opportunities/assets/opportunity-classification-Dm_W66ep.js 13.44 kB │ gzip: 4.55 kB +public/build-qa-opportunities/assets/react-CjVep8xp.js 13.90 kB │ gzip: 4.61 kB +public/build-qa-opportunities/assets/opportunity-search-filters-B1kHkt50.js 14.73 kB │ gzip: 4.72 kB +public/build-qa-opportunities/assets/case-wwTeoOmv.js 14.77 kB │ gzip: 5.38 kB +public/build-qa-opportunities/assets/product-usage-Csn5TQSM.js 14.81 kB │ gzip: 5.30 kB +public/build-qa-opportunities/assets/company-preparation-8z1Qvdvd.js 15.17 kB │ gzip: 4.92 kB +public/build-qa-opportunities/assets/terms-of-use-CUQdwKes.js 15.27 kB │ gzip: 4.20 kB +public/build-qa-opportunities/assets/edit-qyf8y1Qe.js 15.38 kB │ gzip: 5.15 kB +public/build-qa-opportunities/assets/relationship-repair-brZoqSge.js 16.29 kB │ gzip: 5.62 kB +public/build-qa-opportunities/assets/edit-CWwLVVG7.js 16.60 kB │ gzip: 4.88 kB +public/build-qa-opportunities/assets/edit-DeIwdeuK.js 16.93 kB │ gzip: 4.91 kB +public/build-qa-opportunities/assets/search-hMTBVa-Z.js 17.08 kB │ gzip: 5.65 kB +public/build-qa-opportunities/assets/funding-institution-DQq7N6kA.js 17.24 kB │ gzip: 5.83 kB +public/build-qa-opportunities/assets/use-two-factor-auth-D9w0KtSq.js 17.31 kB │ gzip: 6.30 kB +public/build-qa-opportunities/assets/administration-CgUGyaX7.js 17.41 kB │ gzip: 4.30 kB +public/build-qa-opportunities/assets/notifications-COYs8HfG.js 17.61 kB │ gzip: 5.83 kB +public/build-qa-opportunities/assets/ai-CNDnFHJf.js 18.08 kB │ gzip: 6.92 kB +public/build-qa-opportunities/assets/project-dossier-report-BPxivfbL.js 18.98 kB │ gzip: 6.95 kB +public/build-qa-opportunities/assets/submission-DYV8rsdX.js 19.98 kB │ gzip: 6.32 kB +public/build-qa-opportunities/assets/opportunity-resources-B8WM1sIO.js 20.19 kB │ gzip: 7.22 kB +public/build-qa-opportunities/assets/about-D8OtIyBz.js 20.87 kB │ gzip: 6.99 kB +public/build-qa-opportunities/assets/zones-CAkKqhGW.js 21.16 kB │ gzip: 5.22 kB +public/build-qa-opportunities/assets/service-editor-Bu5x27nt.js 21.30 kB │ gzip: 6.58 kB +public/build-qa-opportunities/assets/context-D8SLahjT.js 21.54 kB │ gzip: 6.99 kB +public/build-qa-opportunities/assets/declaration-Bzl3lixx.js 22.15 kB │ gzip: 7.14 kB +public/build-qa-opportunities/assets/Combination-BBDI-RbM.js 23.03 kB │ gzip: 8.24 kB +public/build-qa-opportunities/assets/desk-internal-operations-BwHIs_KM.js 23.14 kB │ gzip: 6.53 kB +public/build-qa-opportunities/assets/education-CFNgleBU.js 23.40 kB │ gzip: 8.19 kB +public/build-qa-opportunities/assets/investment-opportunity-choices-D56R3GRo.js 24.28 kB │ gzip: 8.06 kB +public/build-qa-opportunities/assets/preparation-rSUWHMu_.js 24.56 kB │ gzip: 6.73 kB +public/build-qa-opportunities/assets/utils-CCDJ05Ll.js 27.31 kB │ gzip: 8.71 kB +public/build-qa-opportunities/assets/business-wizard-BQMAm94O.js 28.60 kB │ gzip: 9.42 kB +public/build-qa-opportunities/assets/proactive-investment-DfIQuXvs.js 28.65 kB │ gzip: 9.16 kB +public/build-qa-opportunities/assets/lead-vUXgd2g8.js 29.03 kB │ gzip: 9.96 kB +public/build-qa-opportunities/assets/preparation-Ck3nokWs.js 29.32 kB │ gzip: 8.51 kB +public/build-qa-opportunities/assets/form-ClhCCJAo.js 29.80 kB │ gzip: 8.04 kB +public/build-qa-opportunities/assets/profile-Chg149hk.js 30.71 kB │ gzip: 9.33 kB +public/build-qa-opportunities/assets/welcome-bviWRO1E.js 30.73 kB │ gzip: 6.49 kB +public/build-qa-opportunities/assets/manage-GV_WvYCX.js 31.17 kB │ gzip: 7.63 kB +public/build-qa-opportunities/assets/security-DsABJZBj.js 32.11 kB │ gzip: 10.06 kB +public/build-qa-opportunities/assets/editor-CWbYf7YK.js 35.03 kB │ gzip: 11.76 kB +public/build-qa-opportunities/assets/case-Kr0q2Gh8.js 37.38 kB │ gzip: 10.33 kB +public/build-qa-opportunities/assets/room-Bg8k9V54.js 38.25 kB │ gzip: 12.14 kB +public/build-qa-opportunities/assets/simulation-form-CVV000gF.js 39.43 kB │ gzip: 10.27 kB +public/build-qa-opportunities/assets/request-D6rDWCh-.js 41.86 kB │ gzip: 11.29 kB +public/build-qa-opportunities/assets/detail-D8tQRv3B.js 46.42 kB │ gzip: 15.18 kB +public/build-qa-opportunities/assets/PortalInvestidor-C7BfhVCR.js 48.72 kB │ gzip: 14.86 kB +public/build-qa-opportunities/assets/business-financial-plan-s1uoiktr.js 49.19 kB │ gzip: 11.98 kB +public/build-qa-opportunities/assets/catalogue-DqISkm8V.js 49.97 kB │ gzip: 16.08 kB +public/build-qa-opportunities/assets/zone-DEQbIXLp.js 62.43 kB │ gzip: 15.63 kB +public/build-qa-opportunities/assets/show-vTvBmoW9.js 68.49 kB │ gzip: 21.16 kB +public/build-qa-opportunities/assets/opportunity-draft-C7XuiI1O.js 70.65 kB │ gzip: 20.89 kB +public/build-qa-opportunities/assets/document-zip-bqFSTgG4.js 95.97 kB │ gzip: 28.50 kB +public/build-qa-opportunities/assets/leaflet-src-CUWrUTR2.js 148.82 kB │ gzip: 43.39 kB +public/build-qa-opportunities/assets/app-Bez7gBWz.js 221.29 kB │ gzip: 71.24 kB +public/build-qa-opportunities/assets/dist-BRfsww6z.js 307.99 kB │ gzip: 96.67 kB +public/build-qa-opportunities/assets/document-word-CqTGSyXu.js 403.66 kB │ gzip: 115.44 kB +public/build-qa-opportunities/assets/services-DR6lbhJH.js 965.92 kB │ gzip: 226.48 kB +public/build-qa-opportunities/assets/project-dossier-a3IvmE5B.js 1,009.94 kB │ gzip: 274.44 kB + +✓ built in 45.51s +[plugin builtin:vite-reporter] +(!) Some chunks are larger than 500 kB after minification. Consider: +- Using dynamic import() to code-split the application +- Use build.rolldownOptions.output.codeSplitting to improve chunking: +https://rolldown.rs/reference/OutputOptions.codeSplitting +- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit. +[PLUGIN_TIMINGS] Your build spent 99% of 45.5s inside plugin hooks (44.9s). +Measured inside the callback, so queue time is excluded and time the callback itself awaited is not: + - @rolldown/plugin-babel transform (86%, 39.2s, 411 calls) + - @tailwindcss/vite:generate:build transform (10%, 4.6s, 3 calls) +Those rows are 96% of the build; the rest of the 99% is below. +Not measurable — 1 hook whose calls overlap, so elapsed time covers work other calls were doing. Profile with `node +--cpu-prof`: + - vite:css transform (3 calls) +See https://rolldown.rs/reference/InputOptions.checks#plugintimings for more details. diff --git a/outputs/offers-private-phase5-20261114/focused.log b/outputs/offers-private-phase5-20261114/focused.log new file mode 100644 index 00000000..4b2cea7c --- /dev/null +++ b/outputs/offers-private-phase5-20261114/focused.log @@ -0,0 +1 @@ +{"tool":"phpunit","result":"passed","tests":12,"passed":12,"assertions":151,"duration_ms":153336} diff --git a/outputs/offers-private-phase5-20261114/focused.xml b/outputs/offers-private-phase5-20261114/focused.xml new file mode 100644 index 00000000..b1a4007f --- /dev/null +++ b/outputs/offers-private-phase5-20261114/focused.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/outputs/offers-private-phase5-20261114/global-format.log b/outputs/offers-private-phase5-20261114/global-format.log new file mode 100644 index 00000000..3c3aa955 --- /dev/null +++ b/outputs/offers-private-phase5-20261114/global-format.log @@ -0,0 +1,73 @@ + +> check +> vp check + +resources/js/components/funding-classification.tsx (2ms) +resources/js/components/institutional-source/source-text.tsx (0ms) +resources/js/components/password-input.tsx (1ms) +resources/js/components/sector-entry.tsx (32ms) +resources/js/components/sector-resource-reference.tsx (4ms) +resources/js/components/simulation-form.tsx (109ms) +resources/js/components/simulation-irpc-annual.tsx (9ms) +resources/js/components/structure-geography.tsx (2ms) +resources/js/components/tempo-header-tools.tsx (6ms) +resources/js/lib/investor-path-stage.ts (0ms) +resources/js/lib/irpc-annual-settlement.ts (11ms) +resources/js/pages/funding/assisted.tsx (3ms) +resources/js/pages/funding/manage.tsx (2ms) +resources/js/pages/funding/saved.tsx (2ms) +resources/js/pages/investment/catalogue.tsx (16ms) +resources/js/pages/investment/detail.tsx (4ms) +resources/js/pages/investor-path/index.tsx (3ms) +resources/js/pages/investor-path/preparation.tsx (11ms) +resources/js/pages/investor-path/show.tsx (35ms) +resources/js/pages/management/demography-install.tsx (4ms) +resources/js/pages/portal/fiscal-forms.tsx (8ms) +resources/js/pages/portal/search.tsx (32ms) +resources/js/translation/page-translator.tsx (1ms) +resources/js/translation/service.ts (2ms) +resources/js/translation/types.ts (0ms) +resources/js/translation/utils.ts (3ms) +tests/frontend/cluster-editorial.test.mjs (0ms) +tests/frontend/dashboard-navigation-sections.test.mjs (0ms) +tests/frontend/editorial-completion.test.mjs (0ms) +tests/frontend/editorial-dashboard-manifest.test.mjs (0ms) +tests/frontend/editorial-dashboard-presentation.test.mjs (0ms) +tests/frontend/editorial-edition.test.mjs (0ms) +tests/frontend/education-review.test.mjs (1ms) +tests/frontend/fiscal-form-entry.test.mjs (0ms) +tests/frontend/funding-directory.test.mjs (1ms) +tests/frontend/global-search.test.mjs (1ms) +tests/frontend/helpers/irpc-advance-period-case.mjs (0ms) +tests/frontend/helpers/irpc-annual-noncivil-case.mjs (0ms) +tests/frontend/helpers/irpc-investment-combination-case.mjs (0ms) +tests/frontend/investment-map-indicators.test.mjs (6ms) +tests/frontend/investment-preparation.test.mjs (9ms) +tests/frontend/irpc-advance-cashflow-ui.test.mjs (0ms) +tests/frontend/irpc-advance-cashflow.test.mjs (0ms) +tests/frontend/irpc-advance-period-ui.test.mjs (0ms) +tests/frontend/irpc-advance-period.test.mjs (0ms) +tests/frontend/irpc-annual-noncivil-ui.test.mjs (0ms) +tests/frontend/irpc-annual-noncivil.test.mjs (0ms) +tests/frontend/irpc-investment-carry-combination.test.mjs (0ms) +tests/frontend/irpc-pec-correction.test.mjs (11ms) +tests/frontend/irpc-pec-last-amount.test.mjs (0ms) +tests/frontend/irpc-pec-obligation-ui.test.mjs (0ms) +tests/frontend/irpc-pec-obligation.test.mjs (1ms) +tests/frontend/irpc-ppc-obligation-ui.test.mjs (0ms) +tests/frontend/licensing-stage.test.mjs (4ms) +tests/frontend/navigation-sidebar-render.test.mjs (0ms) +tests/frontend/opportunity-curation.test.mjs (0ms) +tests/frontend/opportunity-information.test.mjs (0ms) +tests/frontend/portal-institutional-contact.test.mjs (0ms) +tests/frontend/portal-navigation.test.mjs (1ms) +tests/frontend/portal-six-group-menu.test.mjs (1ms) +tests/frontend/sector-resource-reference.test.mjs (2ms) + +Found formatting issues in 61 files (5491ms, 6 threads). Run `vp check --fix` to fix them. +npm.cmd : error: Formatting issues found +At line:2 char:1 ++ npm.cmd run check *> outputs/offers-private-phase5-20261114/global-fo ... ++ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + CategoryInfo : NotSpecified: (error:...ng issues found:String) [], RemoteException + + FullyQualifiedErrorId : NativeCommandError diff --git a/outputs/offers-private-phase5-20261114/qa-result.json b/outputs/offers-private-phase5-20261114/qa-result.json new file mode 100644 index 00000000..32b60a46 --- /dev/null +++ b/outputs/offers-private-phase5-20261114/qa-result.json @@ -0,0 +1,170 @@ +{ + "phase": 5, + "base": "e431de8b423a4b64b9875ca7a8627e3e4b3c9c35", + "state": "implemented and technically verified in QA; awaits acceptance; no installation or effective publication", + "scope": "Ofertas only, phases one to five preserved", + "database": { + "host": "127.0.0.1", + "port": 55441, + "name": "offers_phase1_20261114_test", + "data": "synthetic, existing guards and rollback" + }, + "verified_runs": { + "focused.log": { + "tool": "phpunit", + "result": "passed", + "tests": 12, + "passed": 12, + "assertions": 151, + "duration_ms": 153336 + }, + "regression.log": { + "tool": "phpunit", + "result": "passed", + "tests": 59, + "passed": 59, + "assertions": 780, + "duration_ms": 447792 + } + }, + "integrated_suites": { + "Tests\\Feature\\PrivateOfferOperationsTest": { + "tests": 13, + "assertions": 180, + "errors": 0, + "failures": 0 + }, + "Tests\\Feature\\PrivateOfferConfidentialityTest": { + "tests": 11, + "assertions": 160, + "errors": 0, + "failures": 0 + }, + "Tests\\Feature\\OfferPublicCatalogueTest": { + "tests": 10, + "assertions": 133, + "errors": 0, + "failures": 0 + }, + "Tests\\Feature\\PrivateOfferPreparationTest": { + "tests": 14, + "assertions": 146, + "errors": 0, + "failures": 0 + }, + "Tests\\Feature\\PrivateOfferEditorialTest": { + "tests": 11, + "assertions": 161, + "errors": 0, + "failures": 0 + } + }, + "checks": { + "pint": "passed", + "phpstan": "passed, zero errors", + "typescript": "passed", + "frontend_lint_format": "eight affected files passed", + "qa_build": "passed, build-final.log", + "html": "real compiled preparation, report, operations list, editorial dashboard and earlier page cases passed", + "git_diff_check": "passed", + "global_format": { + "failures": 61, + "unchanged_git_content": true, + "outside_delta": true, + "same_baseline_list": true + } + }, + "acceptance_matrix": "docs/investmoz/v2/auditoria-portal/ofertas-fase5-fecho-qa-20261114.md", + "matrix_updated": "RF-MN-003 only; all 49 columns and other rows preserved", + "features": { + "withdrawal": "owner, immediate and idempotent, no editorial approval or stale-etag block", + "reactivation": "new operational cycle, exact new editorial validation and owner confirmation; old public snapshot and NDA grants not resurrected", + "validity": "frozen by approval hash, starts at publication UTC, read-time expiry independent of NDA and cron", + "reports": "encrypted grounds and reported source, idempotent receipt, native tasks/dashboard, scoped MFA reviewer, version and etag", + "highlights": "private draft/archive plans only, versioned and audited; is_featured untouched", + "public_consumers": "list/detail/filters/map/API/CSV exclude unavailable or expired snapshots, no-store HTTP; optional expires_at only when known" + }, + "default_configuration": { + "nda": null, + "validity_days": null, + "validity_policy_reference": null, + "expiry_scheduler_enabled": false + }, + "restrictions": { + "effective_publication_enabled": false, + "installations_performed": false, + "operational_data_modified": false, + "mne_modified": false, + "migrations_added": false, + "payments_billing_or_commercial_promotion_enabled": false, + "unrelated_features_started": false, + "dependencies_installed": false + }, + "accepted_commits_preserved": [ + "1f2990e4", + "6e302db6", + "91c18573", + "7691cab3", + "e431de8b" + ], + "pending": [ + "approved real NDA exemplar/version/approval reference", + "legal identification of property evidence types", + "final validity period/policy and legal notices", + "desktop/mobile visual acceptance; last browser connector evidence host unavailable", + "GD/WebP image derivatives, not installed", + "real email delivery from phase two", + "61 pre-existing format failures", + "express authorization for any installation or effective publication" + ], + "source_hash_encoding": "SHA256 of UTF-8 with Git LF normalization", + "source_sha256": { + "app/Console/Commands/ExpirePrivateOffers.php": "19c1a80f73eb063c7c9f26872710954f30f13dea82eaeca3e99190d881960ec5", + "app/Http/Controllers/OfferPublicCatalogueController.php": "04034126a52660e10a6e64207a3aa4fb09a9b03aee045383b240d434477f03ca", + "app/Http/Controllers/PortalEditorialWorkflowController.php": "de74af5fb777033eaf67889e2239b3aa24ec56a758db0a42826367c5c79b8f49", + "app/Http/Controllers/PrivateOfferOperationsController.php": "872ef0dbfae74e07c9ca4181cd2594a365dced8df93f3e4904b374a660a1eade", + "app/Http/Controllers/PrivateOfferPreparationController.php": "32415ac61a948a0bd53f440c728b5e99522017d765910101eb1a7df1d8b6d0d7", + "app/Services/OfferAvailability.php": "72f975c8c01c7a3ccd3cc00d372210b3546dc6bbae1c34d91514b341a7968adf", + "app/Services/OfferPublicCatalogue.php": "7eee7ab9864cc29c2de90b071ff2c3bf3cac093d3adde9e1937ded324b2223c6", + "app/Services/OpportunityProfileWorkflow.php": "2b16a309035514967f9e0290f9167433b4979940aa26f18db2afdb24996d5130", + "app/Services/PrivateOfferConfidentiality.php": "17e02925c3cfe26a4853db272b6d3c2acd1095efac826371a517a603eaa429ca", + "app/Services/PrivateOfferOperations.php": "1b6032e96712f9d19befe76029a47464c3bb52e4f018d858dcc6f8e605cf0cdc", + "app/Services/PrivateOfferPreparation.php": "442d7aa1093001630bdfc581fde3b9f16e4b5b76a4e8d028ba4b3da41b1cfe4d", + "config/private_offers.php": "975e94b3362a60efe60b7c3a227b8e7727e1e0ef63a42f3a1a575ed7c4725f0e", + "docs/investmoz/unification/traceability-and-manual.md": "ba9f42c7c45a58900548fbfb452359b90e90cdcd08f864a90f8da24e2bbdb9f5", + "docs/investmoz/v2/auditoria-portal/matriz-requisitos.csv": "ba2008946615c5f10b6285f725fe32076baabb72612d053b6ab3223fe3ddc3ad", + "docs/investmoz/v2/auditoria-portal/ofertas-fase5-fecho-qa-20261114.md": "4a56d21a4a2d212efc81c780351282846e6f4d970db287f5fc4ebddf75a66ff5", + "resources/js/components/offer-operations.tsx": "ce4a32b422c7a8e33dade2002b9565d53b4294c44f051ab06022fa6fcfc5ee4e", + "resources/js/components/public-offer-card.tsx": "ac8f01a1c9d864b5f83c48774afcd95d1d42005bb0212b15bbc3f6f7258c0db9", + "resources/js/lib/public-offer.ts": "8ec6f030328af26d0ce70665ce82a41102fadf892120b14deb4e90f1ae2a8a48", + "resources/js/pages/management/editorial-workflow.tsx": "9758c6a1cfc4018905c8493777917d316e18a649eb404c868e9c87356d6c273c", + "resources/js/pages/offers/preparation-index.tsx": "50a0feb8ab2c6abf4e725e2c280d05f4a0842d2be49e0c33d2a0a2d5313a361f", + "resources/js/pages/offers/preparation.tsx": "bf7c02232ffa13617f5f65b9fe7704e31410bd622e9270cdbeef92fe5cbcf1f3", + "resources/js/pages/offers/public-detail.tsx": "387c8a9a0013920bf74c85853362671d76131849508a9b86fc2391622cb0c647", + "resources/js/pages/offers/report.tsx": "91f9e32e88c82b1af984226ae81ede188fddaff395743905b8ab8ce483a8f859", + "routes/console.php": "fe352a044273a39d58c1b58f591324879553c8a3deadb0c45aa96ada804f8f2a", + "routes/web.php": "993577842b5cc3cc8cf5f6f136cfe544795e7e838d26c28cde7cb204934d9073", + "tests/Feature/PrivateOfferOperationsTest.php": "f6e60f25543e9a9b15a8f06e563ad1f7e41c31d69b6801daab899619ff7b98e5" + }, + "evidence_sha256": { + "focused.log": "0ee1e92b0d653ff397f493ae55e3dce44b99213dcf3b9d27ee22e6e47fca14d7", + "focused.xml": "e397adfb3d556ad39df603b1dd9a0bbd4983095112663406196cebd394bc304c", + "regression.log": "81d4ae963170ae290c0b0a536e1fb4c227db909b4b3dcfc51c2c22b8745389d7", + "regression.xml": "756c8a2dd7d4713e74994f5a27d3acac8e196592eb16d355e8b2fab51ce03218", + "global-format.log": "b9290879ba5de47cb4ad6194125287d3dc6e82bf626ef6f8a03716d453161912", + "build-final.log": "a00480048296be75d57a95658234841bde8db2510aaad908ba00f068e53b3399" + }, + "overlapping_runs_not_summed": true, + "report": "docs/investmoz/v2/auditoria-portal/ofertas-fase5-fecho-qa-20261114.md", + "acceptance_matrix_section": "Matriz de aceitação das cinco fases", + "commands": [ + "php vendor/bin/phpunit tests/Feature/PrivateOfferOperationsTest.php tests/Feature/PrivateOfferConfidentialityTest.php tests/Feature/OfferPublicCatalogueTest.php tests/Feature/PrivateOfferPreparationTest.php tests/Feature/PrivateOfferEditorialTest.php --log-junit outputs/offers-private-phase5-20261114/regression.xml", + "php vendor/bin/pint --dirty", + "php vendor/bin/phpstan analyse --no-progress --memory-limit=1G", + "npm.cmd run types:check", + "npx.cmd vp check resources/js/components/offer-operations.tsx resources/js/components/public-offer-card.tsx resources/js/lib/public-offer.ts resources/js/pages/offers/report.tsx resources/js/pages/offers/preparation.tsx resources/js/pages/offers/preparation-index.tsx resources/js/pages/offers/public-detail.tsx resources/js/pages/management/editorial-workflow.tsx", + "$env:INVESTMOZ_QA_BUILD='1'; npm.cmd run build", + "npm.cmd run check" + ], + "evidence_normalization": "UTF-8, LF; trailing whitespace removed from build/global-format logs without removing messages" +} diff --git a/outputs/offers-private-phase5-20261114/regression.log b/outputs/offers-private-phase5-20261114/regression.log new file mode 100644 index 00000000..c0047a08 --- /dev/null +++ b/outputs/offers-private-phase5-20261114/regression.log @@ -0,0 +1 @@ +{"tool":"phpunit","result":"passed","tests":59,"passed":59,"assertions":780,"duration_ms":447792} diff --git a/outputs/offers-private-phase5-20261114/regression.xml b/outputs/offers-private-phase5-20261114/regression.xml new file mode 100644 index 00000000..97fe276c --- /dev/null +++ b/outputs/offers-private-phase5-20261114/regression.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/js/components/offer-operations.tsx b/resources/js/components/offer-operations.tsx new file mode 100644 index 00000000..243732f6 --- /dev/null +++ b/resources/js/components/offer-operations.tsx @@ -0,0 +1,550 @@ +import { Link } from '@inertiajs/react'; +import { useEffect, useState } from 'react'; +import { profileJson } from '@/lib/profile-json'; +type Report = { + id: string; + version: number; + status: string; + reason: string; + resolution_reason: string | null; + source_record_version: number; + source_snapshot?: { + title?: string; + summary?: string; + description?: string; + } | null; +}; +type Highlight = { + version: number; + status: string; + label: string; + note: string; + starts_at: string | null; + ends_at: string | null; +}; +type Operation = { + id: number; + etag: string; + availability: { + state: string; + expires_at: string | null; + policy_pending: boolean; + }; + editorial_state: string; + owner: boolean; + reviewer: boolean; + publisher: boolean; + reports: Report[]; + highlight: Highlight | null; + history: { id: string; action: string; at: string; cycle: number }[]; +}; +const labels: Record = { + available: 'Disponível', + withdrawn: 'Retirada', + expired: 'Expirada', + revalidation: 'Aguarda nova validação', + unavailable: 'Indisponível', +}; +export default function OfferOperations({ + id, + onChanged, +}: { + id: number; + onChanged: () => void; +}) { + const [value, setValue] = useState(null); + const [message, setMessage] = useState(''); + const [busy, setBusy] = useState(false); + const [reason, setReason] = useState(''); + const [confirm, setConfirm] = useState(false); + const [plan, setPlan] = useState({ + label: '', + starts_at: '', + ends_at: '', + note: '', + status: 'draft', + }); + const base = `/minhas-oportunidades/ofertas/${id}`; + useEffect(() => { + let active = true; + profileJson(`${base}/operacoes`, {}, 'GET') + .then((result) => { + if (active) { + setValue(result); + if (result.highlight) + setPlan({ + label: result.highlight.label, + starts_at: result.highlight.starts_at ?? '', + ends_at: result.highlight.ends_at ?? '', + note: result.highlight.note, + status: result.highlight.status, + }); + } + }) + .catch((error) => { + if (active) setMessage(error.message); + }); + return () => { + active = false; + }; + }, [base]); + async function command(path: string, data: object) { + if (busy) return; + setBusy(true); + setMessage(''); + try { + const result = await profileJson(`${base}/${path}`, data); + setValue(result); + setConfirm(false); + onChanged(); + setMessage( + 'Operação guardada em privado. A publicação permanece bloqueada.', + ); + } catch (error) { + setMessage( + error instanceof Error + ? error.message + : 'Não foi possível concluir.', + ); + } finally { + setBusy(false); + } + } + async function refresh(loadPlan = false) { + setBusy(true); + try { + const next: Operation = await profileJson( + `${base}/operacoes`, + {}, + 'GET', + ); + setValue((previous) => + loadPlan + ? next + : { ...next, highlight: previous?.highlight ?? null }, + ); + if (loadPlan) + setPlan({ + label: next.highlight?.label ?? '', + starts_at: next.highlight?.starts_at ?? '', + ends_at: next.highlight?.ends_at ?? '', + note: next.highlight?.note ?? '', + status: next.highlight?.status ?? 'draft', + }); + setConfirm(false); + onChanged(); + setMessage( + loadPlan + ? 'Plano guardado carregado para conferência.' + : 'Disponibilidade e denúncias actualizadas. As observações e o plano local foram preservados.', + ); + } catch (error) { + setMessage( + error instanceof Error + ? error.message + : 'Não foi possível actualizar.', + ); + } finally { + setBusy(false); + } + } + function owner(action: string) { + if (value) + void command('operacoes', { + action, + etag: value.etag, + confirm: true, + reason, + }); + } + return ( +
+

Disponibilidade e acompanhamento

+ + {value && ( + <> +

{labels[value.availability.state] ?? 'Indisponível'}

+

+ {value.availability.policy_pending + ? 'Prazo de validade por definir.' + : value.availability.expires_at + ? `Validade até ${new Date(value.availability.expires_at).toLocaleString('pt-MZ')}.` + : 'A contagem de validade inicia-se com a publicação da versão.'}{' '} + O NDA tem controlo independente. +

+ {value.owner && ( +
+