diff --git a/.env.example b/.env.example index 0eb72f1a..f2fe62ce 100644 --- a/.env.example +++ b/.env.example @@ -119,3 +119,8 @@ INVESTMOZ_TAX_RULE_APPROVERS= INVESTMOZ_TAX_RULE_PUBLISHERS= INVESTMOZ_ZONE_TERRITORY_REVIEWERS= + +# Local LibreTranslate service and installed language models. +LIBRETRANSLATE_URL=http://127.0.0.1:5000 +LIBRETRANSLATE_TIMEOUT=60 +LIBRETRANSLATE_API_KEY= diff --git a/app/Http/Controllers/Api/TranslationApiController.php b/app/Http/Controllers/Api/TranslationApiController.php new file mode 100644 index 00000000..7f4c8a59 --- /dev/null +++ b/app/Http/Controllers/Api/TranslationApiController.php @@ -0,0 +1,58 @@ +timeout(config('services.translation.timeout'))->get($this->url().'/languages'); + if ($response->successful() && is_array($response->json())) { + return response()->json($response->json())->header('Cache-Control', 'no-store'); + } + } catch (Throwable) { + } + + return response()->json(['message' => 'Serviço de tradução temporariamente indisponível.'], 503); + } + + public function translate(Request $request): JsonResponse + { + $data = $request->validate([ + 'text' => 'required|string|max:10000', + 'source_lang' => 'required|string|max:20', + 'target_lang' => 'required|string|max:20', + 'is_html' => 'boolean', + ]); + if ($data['source_lang'] === $data['target_lang']) { + return response()->json(['original_text' => $data['text'], 'translated_text' => $data['text'], 'source_lang' => $data['source_lang'], 'target_lang' => $data['target_lang']])->header('Cache-Control', 'no-store'); + } + + $payload = ['q' => $data['text'], 'source' => $data['source_lang'], 'target' => $data['target_lang'], 'format' => ($data['is_html'] ?? false) ? 'html' : 'text']; + if (config('services.translation.api_key')) { + $payload['api_key'] = config('services.translation.api_key'); + } + try { + $response = Http::connectTimeout(2)->timeout(config('services.translation.timeout'))->post($this->url().'/translate', $payload); + $translated = $response->json('translatedText'); + if ($response->successful() && is_string($translated) && $translated !== '') { + return response()->json(['original_text' => $data['text'], 'translated_text' => $translated, 'source_lang' => $data['source_lang'], 'target_lang' => $data['target_lang']])->header('Cache-Control', 'no-store'); + } + } catch (Throwable) { + } + + return response()->json(['message' => 'Serviço de tradução temporariamente indisponível.'], 503)->header('Cache-Control', 'no-store'); + } + + private function url(): string + { + return rtrim((string) config('services.translation.url'), '/'); + } +} diff --git a/app/Http/Controllers/GlobalSearchController.php b/app/Http/Controllers/GlobalSearchController.php new file mode 100644 index 00000000..ca7e97b5 --- /dev/null +++ b/app/Http/Controllers/GlobalSearchController.php @@ -0,0 +1,173 @@ +validate(['q' => ['nullable', 'string', 'max:120'], 'page' => ['nullable', 'integer', 'min:1']]); + $term = trim($input['q'] ?? ''); + $tokens = $search->tokens($term); + $results = ['data' => [], 'total' => 0, 'current_page' => 1, 'last_page' => 1, 'per_page' => 5, 'unavailable' => []]; + if ($tokens !== []) { + $factories = [ + 'Páginas e serviços' => function () use ($search, $tokens, $term) { + $pages = array_values(array_filter($this->pages(), fn ($page) => $search->matches(implode(' ', $page), $tokens))); + usort($pages, fn ($a, $b) => $search->score($b['title'], $term, $tokens) <=> $search->score($a['title'], $term, $tokens) + ?: strcmp($a['title'], $b['title']) ?: strcmp($a['url'], $b['url'])); + + return ['count' => count($pages), 'fetch' => fn ($offset, $limit) => array_slice($pages, $offset, $limit)]; + }, + 'Oportunidades' => function () use ($catalogue, $search, $tokens, $term) { + $query = $catalogue->query(['include_national' => true]); + foreach ($tokens as $token) { + $query->where(function ($match) use ($catalogue, $search, $token) { + $search->orFields($match, ['opportunities.title', 'opportunities.summary', 'opportunities.description', 'opportunities.reference', 'opportunities.province'], $token); + $match->orWhereHas('sector', fn ($sector) => $search->constrain($sector, ['name'], [$token])); + $match->orWhereExists(fn ($location) => $location->selectRaw('1')->from('geo_locations as search_location') + ->whereIn('search_location.id', app(InvestmentGeography::class)->publicReferences()->select('id')) + ->where(fn ($name) => $search->orFields($name, ['search_location.name'], $token)) + ->where(fn ($direct) => $direct->whereColumn('search_location.id', 'opportunities.primary_territory_id') + ->orWhereExists(fn ($link) => $link->selectRaw('1')->from('opportunity_locations as search_link') + ->whereColumn('search_link.opportunity_id', 'opportunities.id')->whereColumn('search_link.geo_location_id', 'search_location.id')))); + if (preg_match('/^[a-z0-9]+$/', $token)) { + $match->orWhereIn('opportunities.id', $catalogue->query(['q' => $token, 'include_national' => true])->reorder()->select('opportunities.id')); + } + }); + } + + return $this->source($query, $search, $term, $tokens, 'opportunities.title', 'opportunities.id', + fn ($r) => ['title' => $r->title, 'description' => $r->summary, 'url' => '/oportunidades/'.rawurlencode($r->slug)]); + }, + 'Negócios' => function () use ($search, $tokens, $term) { + $query = ExistingBusiness::query()->published(); + $search->constrain($query, ['public_title', 'activity_code', 'objective'], $tokens); + + return $this->source($query, $search, $term, $tokens, 'public_title', 'existing_businesses.id', + fn ($r) => ['title' => $r->public_title, 'description' => $r->objective, 'url' => '/negocios/'.rawurlencode($r->public_slug)]); + }, + 'Carteira: projectos, clusters e corredores' => function () use ($search, $tokens, $term) { + $query = InvestmentPortfolioController::visible(); + $search->constrain($query, ['investment_dossiers.title', 'investment_dossiers.content'], $tokens); + + return $this->source($query, $search, $term, $tokens, 'investment_dossiers.title', 'investment_dossiers.id', + fn ($r) => ['title' => $r->title, 'description' => $r->corridor_id ? 'Corredor' : ($r->cluster_id ? 'Cluster' : 'Projecto âncora'), 'url' => '/carteira/'.$r->id]); + }, + 'Documentos e legislação' => function () use ($search, $tokens, $term) { + $query = DB::table('portal_resources as r')->join('documents as d', 'd.id', '=', 'r.document_id') + ->where('r.published', true)->where('r.rights_status', '!=', 'restricted')->where('d.rights_status', '!=', 'restricted') + ->where('d.status', 'active')->where('d.data_classification', 'public')->whereNull('d.deleted_at') + ->where('d.malware_scan_status', 'clean')->whereColumn('d.malware_scanned_sha256', 'd.sha256'); + $search->constrain($query, ['r.title', 'r.description', 'r.document_number', 'd.issuer', 'r.source_reference', 'r.category', 'r.topic'], $tokens); + $query->select('r.id', 'r.title', 'r.description'); + + return $this->source($query, $search, $term, $tokens, 'r.title', 'r.id', + fn ($r) => ['title' => $r->title, 'description' => $r->description, 'url' => '/documentos/'.$r->id]); + }, + 'Artigos' => function () use ($search, $tokens, $term) { + if (! Schema::hasColumn('portal_resources', 'editorial_content')) { + return ['count' => 0, 'fetch' => fn ($offset, $limit) => []]; + } + $query = app(EditorialReading::class)->publicResources() + ->whereRaw("nullif(btrim(r.editorial_content->>'body'), '') is not null"); + $search->constrain($query, ['r.title', "(r.editorial_content->>'body')"], $tokens); + $query->select('r.id', 'r.title')->selectRaw("r.editorial_content->>'body' as body"); + + return $this->source($query, $search, $term, $tokens, 'r.title', 'r.id', + fn ($r) => ['title' => $r->title, 'description' => Str::limit(trim(strip_tags($r->body)), 220), 'url' => '/artigos/'.$r->id]); + }, + 'Sectores' => function () use ($search, $tokens, $term) { + $query = Sector::query()->where('is_active', true)->whereNotNull('published_at'); + $search->constrain($query, ['name', 'description', 'short_description'], $tokens); + + return $this->source($query, $search, $term, $tokens, 'name', 'sectors.id', + fn ($r) => ['title' => $r->name, 'description' => $r->short_description, 'url' => SectorOrganization::href($r->slug)]); + }, + 'Localizações' => function () use ($search, $tokens, $term) { + $query = app(InvestmentGeography::class)->publicReferences() + ->whereIn('location_type', ['province', 'district', 'admin_post', 'locality']); + $search->constrain($query, ['geo_locations.name', 'geo_locations.source_code'], $tokens); + $query->select('geo_locations.id', 'geo_locations.name', 'geo_locations.location_type'); + + return $this->source($query, $search, $term, $tokens, 'geo_locations.name', 'geo_locations.id', + fn ($r) => ['title' => $r->name, 'description' => ['province' => 'Província', 'district' => 'Distrito', 'admin_post' => 'Posto administrativo', 'locality' => 'Localidade'][$r->location_type], + 'url' => '/oportunidades?'.http_build_query([$r->location_type.'_id' => $r->id])]); + }, + 'Zonas de investimento' => function () use ($search, $tokens, $term) { + $query = app(InvestmentZones::class)->visible(); + $search->constrain($query, ['name', 'code'], $tokens); + + return $this->source($query, $search, $term, $tokens, 'name', 'investment_zones.id', + fn ($r) => ['title' => $r->name, 'description' => $r->code, 'url' => '/zonas/'.$r->id]); + }, + 'Parcerias' => function () use ($search, $tokens, $term) { + $query = DB::table('portal_partnerships as p')->where('p.status', 'published'); + $search->constrain($query, ['p.title', 'p.sector', 'p.province'], $tokens); + $query->select('p.id', 'p.title', 'p.sector', 'p.province'); + + return $this->source($query, $search, $term, $tokens, 'p.title', 'p.id', + fn ($r) => ['title' => $r->title, 'description' => implode(' · ', array_filter([$r->sector, $r->province])), 'url' => '/parcerias/'.$r->id]); + }, + ]; + $results = $federated->page($factories, (int) ($input['page'] ?? 1), 5, + fn ($item) => $search->score($item['title'], $term, $tokens)); + } + + return Inertia::render('portal/search', ['query' => $term, 'results' => $results]); + } + + private function source($query, SearchTerms $search, string $term, array $tokens, string $title, string $id, callable $present): array + { + $count = (clone $query)->reorder()->count(); + $query->reorder(); + $search->orderByRelevance($query, $title, $term, $tokens, $id); + + return ['count' => $count, 'fetch' => fn ($offset, $limit) => (clone $query)->offset($offset)->limit($limit)->get()->map($present)->all()]; + } + + /** Public service entry points and terms used to find them. */ + private function pages(): array + { + return [ + ['title' => 'Como investir em Moçambique', 'description' => 'Procedimentos e etapas para investir', 'url' => '/como-investir'], + ['title' => 'Iniciar ou continuar investimento', 'description' => 'Preparar uma ideia e dossier de investimento', 'url' => '/investimentos/iniciar'], + ['title' => 'Oportunidades de investimento', 'description' => 'Pesquisar por sector, localização e maturidade', 'url' => '/oportunidades'], + ['title' => 'Carteira de investimento', 'description' => 'Projectos âncora, clusters e corredores', 'url' => '/carteira'], + ['title' => 'Negócios existentes', 'description' => 'Compra, venda e participação em negócios', 'url' => '/negocios'], + ['title' => 'Sectores de investimento', 'description' => 'Actividades económicas e sectores prioritários', 'url' => '/sectores'], + ['title' => 'Zonas de investimento', 'description' => 'Zonas económicas especiais, ZEE, ZFI e parques industriais', 'url' => '/zonas'], + ['title' => 'Contextos de investimento', 'description' => 'Formas de iniciar e preparar um investimento', 'url' => '/contextos-de-investimento'], + ['title' => 'Documentos e formulários', 'description' => 'Acervo documental e formulários para investidores e empresas', 'url' => '/documentos'], + ['title' => 'Formulário do investidor', 'description' => 'Dados e contacto para investir', 'url' => '/formularios/investidor'], + ['title' => 'Formulário de empresa', 'description' => 'Dados e contacto para empresas', 'url' => '/formularios/empresa'], + ['title' => 'Legislação e incentivos', 'description' => 'Leis, decretos, benefícios e enquadramento legal', 'url' => '/legislacao'], + ['title' => 'Simulador fiscal', 'description' => 'Simular impostos e cenários fiscais de investimento', 'url' => '/simulador-fiscal'], + ['title' => 'Formulários fiscais', 'description' => 'Preparação dos formulários IVA e IRPC', 'url' => '/formularios/fiscais'], + ['title' => 'Terra e DUAT', 'description' => 'Apoio ao uso e aproveitamento da terra', 'url' => '/terra-duat'], + ['title' => 'Bolsa de parcerias', 'description' => 'Encontrar parceiros e propostas', 'url' => '/parcerias'], + ['title' => 'Apoio ao investidor', 'description' => 'Contactos e pedido de apoio', 'url' => '/apoio'], + ['title' => 'Educação para investimento', 'description' => 'Conteúdos educativos e orientação', 'url' => '/educacao'], + ['title' => 'Sobre o InvesteMoz', 'description' => 'Informação institucional do portal', 'url' => '/sobre'], + ['title' => 'Contacto', 'description' => 'Contactar o portal InvesteMoz', 'url' => '/contacto'], + ['title' => 'Política de privacidade', 'description' => 'Privacidade e tratamento de dados pessoais', 'url' => '/politica-de-privacidade'], + ['title' => 'Termos de uso', 'description' => 'Condições de utilização do portal', 'url' => '/termos-de-uso'], + ]; + } +} diff --git a/app/Services/FederatedSearch.php b/app/Services/FederatedSearch.php new file mode 100644 index 00000000..dfeb49b3 --- /dev/null +++ b/app/Services/FederatedSearch.php @@ -0,0 +1,78 @@ + $factories */ + public function page(array $factories, int $requestedPage, int $size = 5, ?callable $score = null): array + { + $sources = []; + $unavailable = []; + foreach ($factories as $label => $factory) { + try { + $sources[$label] = $factory(); + } catch (QueryException $exception) { + report($exception); + $unavailable[] = $label; + } + } + + // If a source fails after its count, remove it and recompute the page offsets. + do { + $failed = false; + $total = array_sum(array_column($sources, 'count')); + $last = max(1, (int) ceil($total / $size)); + $page = min(max(1, $requestedPage), $last); + $first = ($page - 1) * $size; + $items = []; + $positions = array_fill_keys(array_keys($sources), 0); + $buffers = array_fill_keys(array_keys($sources), []); + for ($position = 0; $position < min($total, $first + $size); $position++) { + $best = null; + $bestScore = PHP_INT_MIN; + foreach ($sources as $label => $source) { + if ($buffers[$label] === [] && $positions[$label] < $source['count']) { + try { + $batch = iterator_to_array((function () use ($source, $positions, $label, $size) { + yield from ($source['fetch'])($positions[$label], $size); + })(), false); + $positions[$label] += count($batch); + $buffers[$label] = $batch; + if ($batch === []) { + $positions[$label] = $source['count']; + } + } catch (QueryException $exception) { + report($exception); + $unavailable[] = $label; + unset($sources[$label]); + $failed = true; + break; + } + } + if ($buffers[$label] === []) { + continue; + } + $candidateScore = $score ? $score($buffers[$label][0]) : 0; + if ($best === null || $candidateScore > $bestScore) { + $best = $label; + $bestScore = $candidateScore; + } + } + if ($failed || $best === null) { + break; + } + $item = array_shift($buffers[$best]); + if ($position >= $first) { + $items[] = [...$item, 'category' => $best]; + } + } + } while ($failed); + + return ['data' => $items, 'total' => $total, 'current_page' => $page, 'last_page' => $last, + 'per_page' => $size, 'unavailable' => $unavailable]; + } +} diff --git a/app/Services/SearchTerms.php b/app/Services/SearchTerms.php new file mode 100644 index 00000000..48c1bf0c --- /dev/null +++ b/app/Services/SearchTerms.php @@ -0,0 +1,112 @@ + */ + public function tokens(string $query): array + { + $normalized = mb_strtolower(Str::ascii($query)); + $tokens = array_values(array_filter(array_unique(array_map( + fn ($word) => trim($word, ".,;:!?\"'()[]{}"), + preg_split('/\s+/u', trim($normalized), -1, PREG_SPLIT_NO_EMPTY) ?: [] + )), fn ($word) => $word !== '')); + if (count($tokens) > 1) { + $meaningful = array_values(array_diff($tokens, ['a', 'as', 'o', 'os', 'de', 'do', 'da', 'dos', 'das', 'em', 'no', 'na', 'nos', 'nas', 'para', 'por', 'e', 'como', 'qual', 'quais', 'um', 'uma'])); + + return $meaningful === [] ? $tokens : $meaningful; + } + + return $tokens; + } + + /** Known Portuguese spelling and number variants; do not infer semantic equivalence. */ + public function variants(string $token): array + { + foreach ([['sector', 'sectores', 'setor', 'setores'], ['projecto', 'projectos', 'projeto', 'projetos'], + ['investir', 'investimento', 'investimentos'], ['investidor', 'investidores'], ['oportunidade', 'oportunidades'], ['documento', 'documentos'], + ['empresa', 'empresas'], ['parceria', 'parcerias'], ['zona', 'zonas'], ['formulario', 'formularios'], + ['provincia', 'provincias'], ['corredor', 'corredores'], ['cluster', 'clusters'], ['imposto', 'impostos'], ['licenca', 'licencas']] as $group) { + if (in_array($token, $group, true)) { + return $group; + } + } + + return [$token]; + } + + public function pattern(string $token): string + { + return '%'.str_replace(['!', '%', '_'], ['!!', '!%', '!_'], $token).'%'; + } + + /** Columns are trusted application constants, never user input. */ + public function orFields($query, array $columns, string $token): void + { + foreach ($columns as $column) { + foreach ($this->variants($token) as $variant) { + $query->orWhereRaw($this->normalizedColumn($column)." LIKE ? ESCAPE '!'", [$this->pattern($variant)]); + } + } + } + + public function constrain($query, array $columns, array $tokens): void + { + foreach ($tokens as $token) { + $query->where(fn ($match) => $this->orFields($match, $columns, $token)); + } + } + + public function matches(string $text, array $tokens): bool + { + $text = mb_strtolower(Str::ascii($text)); + foreach ($tokens as $token) { + if (! array_filter($this->variants($token), fn ($variant) => str_contains($text, $variant))) { + return false; + } + } + + return true; + } + + private function normalizedColumn(string $column): string + { + return "translate(lower(coalesce({$column}::text, '')), 'áàâãäéèêëíìîïóòôõöúùûüçñ', 'aaaaaeeeeiiiiooooouuuucn')"; + } + + public function score(string $title, string $phrase, array $tokens): int + { + $title = mb_strtolower(Str::ascii($title)); + $phrase = mb_strtolower(Str::ascii(trim($phrase))); + $score = $title === $phrase ? 1000 : (str_contains($title, $phrase) ? 400 : 0); + foreach ($tokens as $token) { + if ($this->matches($title, [$token])) { + $score += 20; + } + } + + return $score; + } + + /** The same title score used for in-memory pages, with bound search terms. */ + public function orderByRelevance($query, string $titleColumn, string $phrase, array $tokens, string $tieColumn): void + { + $column = $this->normalizedColumn($titleColumn); + $phrase = mb_strtolower(Str::ascii(trim($phrase))); + $sql = "CASE WHEN {$column} = ? THEN 1000 WHEN {$column} LIKE ? ESCAPE '!' THEN 400 ELSE 0 END"; + $bindings = [$phrase, $this->pattern($phrase)]; + foreach ($tokens as $token) { + $conditions = []; + foreach ($this->variants($token) as $variant) { + $conditions[] = "{$column} LIKE ? ESCAPE '!'"; + $bindings[] = $this->pattern($variant); + } + $sql .= ' + CASE WHEN ('.implode(' OR ', $conditions).') THEN 20 ELSE 0 END'; + } + $query->orderByRaw('('.$sql.') DESC', $bindings)->orderBy($titleColumn)->orderBy($tieColumn); + } +} diff --git a/config/services.php b/config/services.php index febaaea2..892da0b1 100644 --- a/config/services.php +++ b/config/services.php @@ -28,6 +28,12 @@ return [ | */ + 'translation' => [ + 'url' => env('LIBRETRANSLATE_URL', 'http://127.0.0.1:5000'), + 'timeout' => (int) env('LIBRETRANSLATE_TIMEOUT', 60), + 'api_key' => env('LIBRETRANSLATE_API_KEY'), + ], + 'postmark' => [ 'key' => env('POSTMARK_API_KEY'), ], diff --git a/docs/investmoz/unification/traceability-and-manual.md b/docs/investmoz/unification/traceability-and-manual.md index 823a91ad..939b156d 100644 --- a/docs/investmoz/unification/traceability-and-manual.md +++ b/docs/investmoz/unification/traceability-and-manual.md @@ -7620,6 +7620,7 @@ Cobertura: identificação/período, 27 campos manuais IVA e 24 campos manuais M Pedido expresso da Tempo: preencher formulário de IVA ou IRPC, em vez de abrir legislação. Base preservada: entrega instalada 789fffd0, Modelo A e aquisições/apuramento já aceites, L6 M20/M22 instalado em 5aaf1a56 e incrementos de L8. Nenhuma perda confirmada neste recorte; não se apagam as aceitações nem se repetem provas fiscais anteriores. Fontes reutilizadas do inventário de 20 exemplares acessíveis em outputs/irpc-l6-values-forms/accessible-source-inventory.json da QA anterior, incluindo temporários identificados; inventário do corpus e originais preservados. M22: três páginas conferidas visualmente nesta revisão, SHA256 4b61bd83d1fbcfd3b7b5c5523cea5923f7d0f65dc4c004eff58e5db38f763c88. Modelo A: correspondência e divergências aceites em modelo-a-reconciliacao-20261021.md; sem nova validação de proveniência. Incremento em preparação QA: entrada directa em Documentos e Formulários, preenchimento manual dos campos correspondidos de IVA e subconjunto M22, identificação/período, revisão, impressão e ficheiro privado para recuperação mediante confirmação. Escolha técnica: sem persistência no servidor nem ligação automática ao dossier neste incremento; não altera schema, dados, permissões ou cálculos. Ficheiro não publicado. IRPC ajustamentos completos, outros regimes, anexos, assinatura e submissão à AT fora da cobertura; L8 permanece parcial. Ainda sem instalação deste incremento. Próximas provas apenas do delta: fidelidade da recuperação, rejeição de ficheiros incompatíveis sem perda, formatos/valores desconhecidos, acesso público e compilação. +<<<<<<< HEAD @@ -9181,3 +9182,5 @@ Próximo recorte dos filtros previstos na matriz: selecção por indicador e per Delta verificado: 12 testes frontend dirigidos aprovados, tipos globais, lint do componente e build QA aprovados. Conferência visual apenas em `127.0.0.2:18644`/`main_visual_20261104_test`, reutilizando as cinco observações sintéticas anteriores, sem novas fixtures ou escrita de dados. Produção em 31/12/2025 devolve uma linha com `125.7500` toneladas; intervalo invertido mostra erro; limpar repõe cinco linhas. As 12 oportunidades do cenário QA mantêm-se. Corrigida e conferida a actualização dos campos de data durante a introdução. Recibo e hashes: `docs/investmoz/unification/quantitative-map-filters-local-20261104.json`; DOM, captura e logs em `outputs/quantitative-filters-20261104/`. **ACT-02 permanece parcial.** Cobertura factual nacional/regional, séries comparáveis, oferta laboral e metodologia quantitativa de potencial económico continuam pendentes conforme fontes aplicáveis. O cenário sintético não é estatística real. Conferência autenticada de Competitividade pela equipa e requisitos amplos V2-07/V2-10 preservados. Sem push, instalação deste delta, migração, importação, publicação automática, escrita activa, alteração de permissões ou reinício de serviços. +======= +>>>>>>> 705dc69d683a451eda1371db7eceb280047b2895 diff --git a/docs/investmoz/v2/auditoria-portal/matriz-requisitos.csv b/docs/investmoz/v2/auditoria-portal/matriz-requisitos.csv index ae70cf05..1454fa23 100644 --- a/docs/investmoz/v2/auditoria-portal/matriz-requisitos.csv +++ b/docs/investmoz/v2/auditoria-portal/matriz-requisitos.csv @@ -395,7 +395,7 @@ MASTER-9dee08d0-0004,"O portal deve servir como um balcão ou agregador facilita MASTER-9dee08d0-0005,Princípios de experiência do utilizador: O portal deve ser desenhado a partir das principais tarefas do investidor:,portal do investidor\Reformulacao do portal do investidor.docx; SHA 9dee08d0ebbbb262651a01ffc7e0ade97323664bda59fe8ce0b07ead82736ce1; w:p 118 | portal do investidor\backup\Reformulacao do portal do investidor 1.docx; SHA efd550fd2a9d2a21785d7b3c140d4608f1ba27c52f156e2709e9fbf388a1a569; w:p 122 | portal do investidor\backup\Reformulacao do portal do investidor v2.docx; SHA 9f5719c8b0cf78855f727d980b77a8f9409fc7f93e303e25d355d009a07d5734; w:p 135,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.",MASTER-9dee08d0-0005: Princípios de experiência do utilizador: O portal deve ser desenhado a partir das principais tarefas do investidor: | MASTER-efd550fd-0005: Princípios de experiência do utilizador: O portal deve ser desenhado a partir das principais tarefas do investidor: | MASTER-9f5719c8-0006: Princípios de experiência do utilizador: O portal deve ser desenhado a partir das principais tarefas do investidor:,P2,MASTER-9dee08d0-0005: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-efd550fd-0005: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-9f5719c8-0006: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas,MASTER-9dee08d0-0005 | MASTER-efd550fd-0005 | MASTER-9f5719c8-0006,3,"[{""id"":""MASTER-9dee08d0-0005"",""pedido"":""Princípios de experiência do utilizador: O portal deve ser desenhado a partir das principais tarefas do investidor:"",""fontes"":[""portal do investidor\\Reformulacao do portal do investidor.docx; SHA 9dee08d0ebbbb262651a01ffc7e0ade97323664bda59fe8ce0b07ead82736ce1; w:p 118""],""aceitacao"":""Princípios de experiência do utilizador: O portal deve ser desenhado a partir das principais tarefas do investidor:"",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""},{""id"":""MASTER-efd550fd-0005"",""pedido"":""Princípios de experiência do utilizador: O portal deve ser desenhado a partir das principais tarefas do investidor:"",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor 1.docx; SHA efd550fd2a9d2a21785d7b3c140d4608f1ba27c52f156e2709e9fbf388a1a569; w:p 122""],""aceitacao"":""Princípios de experiência do utilizador: O portal deve ser desenhado a partir das principais tarefas do investidor:"",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""},{""id"":""MASTER-9f5719c8-0006"",""pedido"":""Princípios de experiência do utilizador: O portal deve ser desenhado a partir das principais tarefas do investidor:"",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor v2.docx; SHA 9f5719c8b0cf78855f727d980b77a8f9409fc7f93e303e25d355d009a07d5734; w:p 135""],""aceitacao"":""Princípios de experiência do utilizador: O portal deve ser desenhado a partir das principais tarefas do investidor:"",""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.",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.",,, MASTER-9dee08d0-0006,"Cada página deve reduzir dúvidas, evitar jargão excessivo, apresentar chamadas de acção claras e oferecer um caminho visível para apoio especializado.",portal do investidor\Reformulacao do portal do investidor.docx; SHA 9dee08d0ebbbb262651a01ffc7e0ade97323664bda59fe8ce0b07ead82736ce1; w:p 124 | portal do investidor\backup\Reformulacao do portal do investidor 1.docx; SHA efd550fd2a9d2a21785d7b3c140d4608f1ba27c52f156e2709e9fbf388a1a569; w:p 128 | portal do investidor\backup\Reformulacao do portal do investidor v2.docx; SHA 9f5719c8b0cf78855f727d980b77a8f9409fc7f93e303e25d355d009a07d5734; w:p 141 | portal do investidor\backup\Reformulacao do portal do investidor v3.docx; SHA 626cae3d584658842ca36b658e7b5b3cc98d87036747e65b29274f0209734e5a; w:p 145,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.","MASTER-9dee08d0-0006: Cada página deve reduzir dúvidas, evitar jargão excessivo, apresentar chamadas de acção claras e oferecer um caminho visível para apoio especializado. | MASTER-efd550fd-0006: Cada página deve reduzir dúvidas, evitar jargão excessivo, apresentar chamadas de acção claras e oferecer um caminho visível para apoio especializado. | MASTER-9f5719c8-0007: Cada página deve reduzir dúvidas, evitar jargão excessivo, apresentar chamadas de acção claras e oferecer um caminho visível para apoio especializado. | MASTER-626cae3d-0004: Cada página deve reduzir dúvidas, evitar jargão excessivo, apresentar chamadas de acção claras e oferecer um caminho visível para apoio especializado.",P2,MASTER-9dee08d0-0006: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-efd550fd-0006: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-9f5719c8-0007: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-626cae3d-0004: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas,MASTER-9dee08d0-0006 | MASTER-efd550fd-0006 | MASTER-9f5719c8-0007 | MASTER-626cae3d-0004,4,"[{""id"":""MASTER-9dee08d0-0006"",""pedido"":""Cada página deve reduzir dúvidas, evitar jargão excessivo, apresentar chamadas de acção claras e oferecer um caminho visível para apoio especializado."",""fontes"":[""portal do investidor\\Reformulacao do portal do investidor.docx; SHA 9dee08d0ebbbb262651a01ffc7e0ade97323664bda59fe8ce0b07ead82736ce1; w:p 124""],""aceitacao"":""Cada página deve reduzir dúvidas, evitar jargão excessivo, apresentar chamadas de acção claras e oferecer um caminho visível para apoio especializado."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""},{""id"":""MASTER-efd550fd-0006"",""pedido"":""Cada página deve reduzir dúvidas, evitar jargão excessivo, apresentar chamadas de acção claras e oferecer um caminho visível para apoio especializado."",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor 1.docx; SHA efd550fd2a9d2a21785d7b3c140d4608f1ba27c52f156e2709e9fbf388a1a569; w:p 128""],""aceitacao"":""Cada página deve reduzir dúvidas, evitar jargão excessivo, apresentar chamadas de acção claras e oferecer um caminho visível para apoio especializado."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""},{""id"":""MASTER-9f5719c8-0007"",""pedido"":""Cada página deve reduzir dúvidas, evitar jargão excessivo, apresentar chamadas de acção claras e oferecer um caminho visível para apoio especializado."",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor v2.docx; SHA 9f5719c8b0cf78855f727d980b77a8f9409fc7f93e303e25d355d009a07d5734; w:p 141""],""aceitacao"":""Cada página deve reduzir dúvidas, evitar jargão excessivo, apresentar chamadas de acção claras e oferecer um caminho visível para apoio especializado."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""},{""id"":""MASTER-626cae3d-0004"",""pedido"":""Cada página deve reduzir dúvidas, evitar jargão excessivo, apresentar chamadas de acção claras e oferecer um caminho visível para apoio especializado."",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor v3.docx; SHA 626cae3d584658842ca36b658e7b5b3cc98d87036747e65b29274f0209734e5a; w:p 145""],""aceitacao"":""Cada página deve reduzir dúvidas, evitar jargão excessivo, apresentar chamadas de acção claras e oferecer um caminho visível para apoio especializado."",""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.",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.",,, MASTER-9dee08d0-0007,"Calculadora ou simulador de incentivos: permitir ao investidor estimar, de forma orientadora, possíveis impostos, benefícios fiscais e requisitos aplicáveis em função da província, do sector, da localização em ZEE ou ZFI e do volume de investimento, deixando claro que o resultado depende de validação legal e institucional.",portal do investidor\Reformulacao do portal do investidor.docx; SHA 9dee08d0ebbbb262651a01ffc7e0ade97323664bda59fe8ce0b07ead82736ce1; w:p 125 | portal do investidor\backup\Reformulacao do portal do investidor 1.docx; SHA efd550fd2a9d2a21785d7b3c140d4608f1ba27c52f156e2709e9fbf388a1a569; w:p 129 | portal do investidor\backup\Reformulacao do portal do investidor v2.docx; SHA 9f5719c8b0cf78855f727d980b77a8f9409fc7f93e303e25d355d009a07d5734; w:p 142 | portal do investidor\backup\Reformulacao do portal do investidor v3.docx; SHA 626cae3d584658842ca36b658e7b5b3cc98d87036747e65b29274f0209734e5a; w:p 146,Balcão/orientação,/balcao-virtual/contextos; /balcao-virtual/dossiers; /balcao-virtual/dossiers/{id}; /balcao-virtual/dossiers/{id}/documentos; /balcao-virtual/dossiers/{id}/documentos/{document}; /balcao-virtual/dossiers/{id}/documentos/{document}/conferencia; /balcao-virtual/dossiers/{id}/etapas; /balcao-virtual/dossiers/{id}/fontes; /balcao-virtual/dossiers/{id}/orientacao; /balcao-virtual/dossiers/{id}/pedidos; /balcao-virtual/dossiers/{id}/pressupostos; /balcao-virtual/dossiers/{id}/recomendacoes,"VirtualDesk, pedidos/casos, anexos, contexto dossier e acompanhamento. 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,"Pedido contextual→gravar→submeter→nova sessão→gestor→mensagem/documentos/resultado, reaproveitando A/B. Critério individual original preservado; cobertura atual ainda não individualizada.","Perfis próprios do Balcão; serviços/campos e decisões externas aplicáveis, sem inventar destinatário/prazo. Nenhuma nova decisão identificada para reconciliar/demonstrar esta cláusula. Decisões específicas só após localizar a regra correspondente.","MASTER-9dee08d0-0007: Calculadora ou simulador de incentivos: permitir ao investidor estimar, de forma orientadora, possíveis impostos, benefícios fiscais e requisitos aplicáveis em função da província, do sector, da localização em ZEE ou ZFI e do volume de investimento, deixando claro que o resultado depende de validação legal e institucional. | MASTER-efd550fd-0007: Calculadora ou simulador de incentivos: permitir ao investidor estimar, de forma orientadora, possíveis impostos, benefícios fiscais e requisitos aplicáveis em função da província, do sector, da localização em ZEE ou ZFI e do volume de investimento, deixando claro que o resultado depende de validação legal e institucional. | MASTER-9f5719c8-0008: Calculadora ou simulador de incentivos: permitir ao investidor estimar, de forma orientadora, possíveis impostos, benefícios fiscais e requisitos aplicáveis em função da província, do sector, da localização em ZEE ou ZFI e do volume de investimento, deixando claro que o resultado depende de validação legal e institucional. | MASTER-626cae3d-0005: Calculadora ou simulador de incentivos: permitir ao investidor estimar, de forma orientadora, possíveis impostos, benefícios fiscais e requisitos aplicáveis em função da província, do sector, da localização em ZEE ou ZFI e do volume de investimento, deixando claro que o resultado depende de validação legal e institucional.",P2,MASTER-9dee08d0-0007: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-efd550fd-0007: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-9f5719c8-0008: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-626cae3d-0005: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas,MASTER-9dee08d0-0007 | MASTER-efd550fd-0007 | MASTER-9f5719c8-0008 | MASTER-626cae3d-0005,4,"[{""id"":""MASTER-9dee08d0-0007"",""pedido"":""Calculadora ou simulador de incentivos: permitir ao investidor estimar, de forma orientadora, possíveis impostos, benefícios fiscais e requisitos aplicáveis em função da província, do sector, da localização em ZEE ou ZFI e do volume de investimento, deixando claro que o resultado depende de validação legal e institucional."",""fontes"":[""portal do investidor\\Reformulacao do portal do investidor.docx; SHA 9dee08d0ebbbb262651a01ffc7e0ade97323664bda59fe8ce0b07ead82736ce1; w:p 125""],""aceitacao"":""Calculadora ou simulador de incentivos: permitir ao investidor estimar, de forma orientadora, possíveis impostos, benefícios fiscais e requisitos aplicáveis em função da província, do sector, da localização em ZEE ou ZFI e do volume de investimento, deixando claro que o resultado depende de validação legal e institucional."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""Balcão/orientação""},{""id"":""MASTER-efd550fd-0007"",""pedido"":""Calculadora ou simulador de incentivos: permitir ao investidor estimar, de forma orientadora, possíveis impostos, benefícios fiscais e requisitos aplicáveis em função da província, do sector, da localização em ZEE ou ZFI e do volume de investimento, deixando claro que o resultado depende de validação legal e institucional."",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor 1.docx; SHA efd550fd2a9d2a21785d7b3c140d4608f1ba27c52f156e2709e9fbf388a1a569; w:p 129""],""aceitacao"":""Calculadora ou simulador de incentivos: permitir ao investidor estimar, de forma orientadora, possíveis impostos, benefícios fiscais e requisitos aplicáveis em função da província, do sector, da localização em ZEE ou ZFI e do volume de investimento, deixando claro que o resultado depende de validação legal e institucional."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""Balcão/orientação""},{""id"":""MASTER-9f5719c8-0008"",""pedido"":""Calculadora ou simulador de incentivos: permitir ao investidor estimar, de forma orientadora, possíveis impostos, benefícios fiscais e requisitos aplicáveis em função da província, do sector, da localização em ZEE ou ZFI e do volume de investimento, deixando claro que o resultado depende de validação legal e institucional."",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor v2.docx; SHA 9f5719c8b0cf78855f727d980b77a8f9409fc7f93e303e25d355d009a07d5734; w:p 142""],""aceitacao"":""Calculadora ou simulador de incentivos: permitir ao investidor estimar, de forma orientadora, possíveis impostos, benefícios fiscais e requisitos aplicáveis em função da província, do sector, da localização em ZEE ou ZFI e do volume de investimento, deixando claro que o resultado depende de validação legal e institucional."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""Balcão/orientação""},{""id"":""MASTER-626cae3d-0005"",""pedido"":""Calculadora ou simulador de incentivos: permitir ao investidor estimar, de forma orientadora, possíveis impostos, benefícios fiscais e requisitos aplicáveis em função da província, do sector, da localização em ZEE ou ZFI e do volume de investimento, deixando claro que o resultado depende de validação legal e institucional."",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor v3.docx; SHA 626cae3d584658842ca36b658e7b5b3cc98d87036747e65b29274f0209734e5a; w:p 146""],""aceitacao"":""Calculadora ou simulador de incentivos: permitir ao investidor estimar, de forma orientadora, possíveis impostos, benefícios fiscais e requisitos aplicáveis em função da província, do sector, da localização em ZEE ou ZFI e do volume de investimento, deixando claro que o resultado depende de validação legal e institucional."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""Balcão/orientação""}]",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.desk_cases: 1 registos; public.desk_requests: 0 registos; public.desk_institutional_links: 0 registos; public.desk_messages: 0 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.","Pedido contextual→gravar→submeter→nova sessão→gestor→mensagem/documentos/resultado, reaproveitando A/B. Critério individual original preservado; cobertura atual ainda não individualizada.","VirtualDesk, pedidos/casos, anexos, contexto dossier e acompanhamento","Perfis próprios do Balcão; serviços/campos e decisões externas aplicáveis, sem inventar destinatário/prazo.",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: balcão. 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.,balcão,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/VirtualDesk.php; app/Services/Ai/InvestorGuidance.php — componentes do domínio encontrados; correspondência integral deste requisito não presumida,InvestorGuidanceTest; 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.",,, -MASTER-9dee08d0-0008,"Suporte multilingue: disponibilizar, no mínimo, versões completas em português e inglês, com alternância visível em todas as páginas principais e tradução consistente dos formulários, guias, mensagens de erro, botões de ação e documentos de apoio essenciais. A inclusão futura de francês ou mandarim deve depender dos públicos-alvo prioritários e da capacidade de manutenção dos conteúdos.",portal do investidor\Reformulacao do portal do investidor.docx; SHA 9dee08d0ebbbb262651a01ffc7e0ade97323664bda59fe8ce0b07ead82736ce1; w:p 126,biblioteca,//; /Simulador; /api/v1/ai/documents; /api/v1/documents/recommendations; /api/v2/investment/admin/legal-sources; /api/v2/investment/admin/legal-sources/{id}/{action}; /apoio; /balcao-virtual; /balcao-virtual/dossiers/{id}/documentos/{document}/conferencia; /como-investir; /documentos; /documentos/{id},"documents, biblioteca, importação autenticada, VerifiedDocumentBinary, jobs/derivadas. 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,Cobrir consulta/importação/doc/derivada/hash/perfil e revogação; preservar original; divulgação separada. Não inferir relação territorial. Critério individual original preservado; cobertura atual ainda não individualizada.,Scanner/worker/GD/WebP e originais privados; ACL por oportunidade/sala diferente de partilha individual não implementada. LegadoOFF. Nenhuma nova decisão identificada para reconciliar/demonstrar esta cláusula. Decisões específicas só após localizar a regra correspondente.,"MASTER-9dee08d0-0008: Suporte multilingue: disponibilizar, no mínimo, versões completas em português e inglês, com alternância visível em todas as páginas principais e tradução consistente dos formulários, guias, mensagens de erro, botões de ação e documentos de apoio essenciais. A inclusão futura de francês ou mandarim deve depender dos públicos-alvo prioritários e da capacidade de manutenção dos conteúdos.",P2,MASTER-9dee08d0-0008: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas,MASTER-9dee08d0-0008,1,"[{""id"":""MASTER-9dee08d0-0008"",""pedido"":""Suporte multilingue: disponibilizar, no mínimo, versões completas em português e inglês, com alternância visível em todas as páginas principais e tradução consistente dos formulários, guias, mensagens de erro, botões de ação e documentos de apoio essenciais. A inclusão futura de francês ou mandarim deve depender dos públicos-alvo prioritários e da capacidade de manutenção dos conteúdos."",""fontes"":[""portal do investidor\\Reformulacao do portal do investidor.docx; SHA 9dee08d0ebbbb262651a01ffc7e0ade97323664bda59fe8ce0b07ead82736ce1; w:p 126""],""aceitacao"":""Suporte multilingue: disponibilizar, no mínimo, versões completas em português e inglês, com alternância visível em todas as páginas principais e tradução consistente dos formulários, guias, mensagens de erro, botões de ação e documentos de apoio essenciais. A inclusão futura de francês ou mandarim deve depender dos públicos-alvo prioritários e da capacidade de manutenção dos conteúdos."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""biblioteca""}]",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.documents: 4 registos; public.portal_resources: 4 registos; public.portal_resource_links: 19 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.",Cobrir consulta/importação/doc/derivada/hash/perfil e revogação; preservar original; divulgação separada. Não inferir relação territorial. Critério individual original preservado; cobertura atual ainda não individualizada.,"documents, biblioteca, importação autenticada, VerifiedDocumentBinary, jobs/derivadas",Scanner/worker/GD/WebP e originais privados; ACL por oportunidade/sala diferente de partilha individual não implementada. LegadoOFF.,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: documentos e legado. 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.,documentos e legado,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/PortalServicesController.php; app/Jobs/ScanDocumentForMalware.php — componentes do domínio encontrados; correspondência integral deste requisito não presumida,"PortalServicesTest, DocumentLibrarySchemaCompatibilityTest, ImportDocumentBatchTest, ClamDScannerIntegrationTest; 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.",,, +MASTER-9dee08d0-0008,"Suporte multilingue: disponibilizar, no mínimo, versões completas em português e inglês, com alternância visível em todas as páginas principais e tradução consistente dos formulários, guias, mensagens de erro, botões de ação e documentos de apoio essenciais. A inclusão futura de francês ou mandarim deve depender dos públicos-alvo prioritários e da capacidade de manutenção dos conteúdos.",portal do investidor\Reformulacao do portal do investidor.docx; SHA 9dee08d0ebbbb262651a01ffc7e0ade97323664bda59fe8ce0b07ead82736ce1; w:p 126,biblioteca,//; /Simulador; /api/v1/ai/documents; /api/v1/documents/recommendations; /api/v2/investment/admin/legal-sources; /api/v2/investment/admin/legal-sources/{id}/{action}; /apoio; /balcao-virtual; /balcao-virtual/dossiers/{id}/documentos/{document}/conferencia; /como-investir; /documentos; /documentos/{id},"documents, biblioteca, importação autenticada, VerifiedDocumentBinary, jobs/derivadas. Capacidade existente associada ao domínio; correspondência integral desta cláusula só quando demonstrada na prova individual. [02/11/2026: levantamento de codigo e incremento parcial preparados; quatro linguas solicitadas, cobertura transversal pendente. Ver roteiro-manual-qa-i0-i3.md.]","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,Cobrir consulta/importação/doc/derivada/hash/perfil e revogação; preservar original; divulgação separada. Não inferir relação territorial. Critério individual original preservado; cobertura atual ainda não individualizada.,Scanner/worker/GD/WebP e originais privados; ACL por oportunidade/sala diferente de partilha individual não implementada. LegadoOFF. Nenhuma nova decisão identificada para reconciliar/demonstrar esta cláusula. Decisões específicas só após localizar a regra correspondente.,"MASTER-9dee08d0-0008: Suporte multilingue: disponibilizar, no mínimo, versões completas em português e inglês, com alternância visível em todas as páginas principais e tradução consistente dos formulários, guias, mensagens de erro, botões de ação e documentos de apoio essenciais. A inclusão futura de francês ou mandarim deve depender dos públicos-alvo prioritários e da capacidade de manutenção dos conteúdos.",P2,MASTER-9dee08d0-0008: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas,MASTER-9dee08d0-0008,1,"[{""id"":""MASTER-9dee08d0-0008"",""pedido"":""Suporte multilingue: disponibilizar, no mínimo, versões completas em português e inglês, com alternância visível em todas as páginas principais e tradução consistente dos formulários, guias, mensagens de erro, botões de ação e documentos de apoio essenciais. A inclusão futura de francês ou mandarim deve depender dos públicos-alvo prioritários e da capacidade de manutenção dos conteúdos."",""fontes"":[""portal do investidor\\Reformulacao do portal do investidor.docx; SHA 9dee08d0ebbbb262651a01ffc7e0ade97323664bda59fe8ce0b07ead82736ce1; w:p 126""],""aceitacao"":""Suporte multilingue: disponibilizar, no mínimo, versões completas em português e inglês, com alternância visível em todas as páginas principais e tradução consistente dos formulários, guias, mensagens de erro, botões de ação e documentos de apoio essenciais. A inclusão futura de francês ou mandarim deve depender dos públicos-alvo prioritários e da capacidade de manutenção dos conteúdos."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""biblioteca""}]",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.documents: 4 registos; public.portal_resources: 4 registos; public.portal_resource_links: 19 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.",Cobrir consulta/importação/doc/derivada/hash/perfil e revogação; preservar original; divulgação separada. Não inferir relação territorial. Critério individual original preservado; cobertura atual ainda não individualizada.,"documents, biblioteca, importação autenticada, VerifiedDocumentBinary, jobs/derivadas",Scanner/worker/GD/WebP e originais privados; ACL por oportunidade/sala diferente de partilha individual não implementada. LegadoOFF.,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: documentos e legado. 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.,documentos e legado,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/PortalServicesController.php; app/Jobs/ScanDocumentForMalware.php — componentes do domínio encontrados; correspondência integral deste requisito não presumida,"PortalServicesTest, DocumentLibrarySchemaCompatibilityTest, ImportDocumentBatchTest, ClamDScannerIntegrationTest; 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.",,, MASTER-9dee08d0-0009,"Área Reservada ou Dashboard do Investidor: permitir ao investidor acompanhar o estado das submissões, visualizar documentos pendentes, receber notificações, agendar reuniões virtuais, consultar histórico de interacções e solicitar assistência direta junto dos serviços de facilitação do investimento.",portal do investidor\Reformulacao do portal do investidor.docx; SHA 9dee08d0ebbbb262651a01ffc7e0ade97323664bda59fe8ce0b07ead82736ce1; w:p 127 | portal do investidor\backup\Reformulacao do portal do investidor 1.docx; SHA efd550fd2a9d2a21785d7b3c140d4608f1ba27c52f156e2709e9fbf388a1a569; w:p 131 | portal do investidor\backup\Reformulacao do portal do investidor v2.docx; SHA 9f5719c8b0cf78855f727d980b77a8f9409fc7f93e303e25d355d009a07d5734; w:p 144 | portal do investidor\backup\Reformulacao do portal do investidor v3.docx; SHA 626cae3d584658842ca36b658e7b5b3cc98d87036747e65b29274f0209734e5a; w:p 148,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.,"MASTER-9dee08d0-0009: Área Reservada ou Dashboard do Investidor: permitir ao investidor acompanhar o estado das submissões, visualizar documentos pendentes, receber notificações, agendar reuniões virtuais, consultar histórico de interacções e solicitar assistência direta junto dos serviços de facilitação do investimento. | MASTER-efd550fd-0009: Área Reservada ou Dashboard do Investidor: permitir ao investidor acompanhar o estado das submissões, visualizar documentos pendentes, receber notificações, agendar reuniões virtuais, consultar histórico de interacções e solicitar assistência direta junto dos serviços de facilitação do investimento. | MASTER-9f5719c8-0010: Área Reservada ou Dashboard do Investidor: permitir ao investidor acompanhar o estado das submissões, visualizar documentos pendentes, receber notificações, agendar reuniões virtuais, consultar histórico de interacções e solicitar assistência direta junto dos serviços de facilitação do investimento. | MASTER-626cae3d-0007: Área Reservada ou Dashboard do Investidor: permitir ao investidor acompanhar o estado das submissões, visualizar documentos pendentes, receber notificações, agendar reuniões virtuais, consultar histórico de interacções e solicitar assistência direta junto dos serviços de facilitação do investimento.",P2,MASTER-9dee08d0-0009: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-efd550fd-0009: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-9f5719c8-0010: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-626cae3d-0007: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas,MASTER-9dee08d0-0009 | MASTER-efd550fd-0009 | MASTER-9f5719c8-0010 | MASTER-626cae3d-0007,4,"[{""id"":""MASTER-9dee08d0-0009"",""pedido"":""Área Reservada ou Dashboard do Investidor: permitir ao investidor acompanhar o estado das submissões, visualizar documentos pendentes, receber notificações, agendar reuniões virtuais, consultar histórico de interacções e solicitar assistência direta junto dos serviços de facilitação do investimento."",""fontes"":[""portal do investidor\\Reformulacao do portal do investidor.docx; SHA 9dee08d0ebbbb262651a01ffc7e0ade97323664bda59fe8ce0b07ead82736ce1; w:p 127""],""aceitacao"":""Área Reservada ou Dashboard do Investidor: permitir ao investidor acompanhar o estado das submissões, visualizar documentos pendentes, receber notificações, agendar reuniões virtuais, consultar histórico de interacções e solicitar assistência direta junto dos serviços de facilitação do investimento."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""CRM/solicitações""},{""id"":""MASTER-efd550fd-0009"",""pedido"":""Área Reservada ou Dashboard do Investidor: permitir ao investidor acompanhar o estado das submissões, visualizar documentos pendentes, receber notificações, agendar reuniões virtuais, consultar histórico de interacções e solicitar assistência direta junto dos serviços de facilitação do investimento."",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor 1.docx; SHA efd550fd2a9d2a21785d7b3c140d4608f1ba27c52f156e2709e9fbf388a1a569; w:p 131""],""aceitacao"":""Área Reservada ou Dashboard do Investidor: permitir ao investidor acompanhar o estado das submissões, visualizar documentos pendentes, receber notificações, agendar reuniões virtuais, consultar histórico de interacções e solicitar assistência direta junto dos serviços de facilitação do investimento."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""CRM/solicitações""},{""id"":""MASTER-9f5719c8-0010"",""pedido"":""Área Reservada ou Dashboard do Investidor: permitir ao investidor acompanhar o estado das submissões, visualizar documentos pendentes, receber notificações, agendar reuniões virtuais, consultar histórico de interacções e solicitar assistência direta junto dos serviços de facilitação do investimento."",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor v2.docx; SHA 9f5719c8b0cf78855f727d980b77a8f9409fc7f93e303e25d355d009a07d5734; w:p 144""],""aceitacao"":""Área Reservada ou Dashboard do Investidor: permitir ao investidor acompanhar o estado das submissões, visualizar documentos pendentes, receber notificações, agendar reuniões virtuais, consultar histórico de interacções e solicitar assistência direta junto dos serviços de facilitação do investimento."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""CRM/solicitações""},{""id"":""MASTER-626cae3d-0007"",""pedido"":""Área Reservada ou Dashboard do Investidor: permitir ao investidor acompanhar o estado das submissões, visualizar documentos pendentes, receber notificações, agendar reuniões virtuais, consultar histórico de interacções e solicitar assistência direta junto dos serviços de facilitação do investimento."",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor v3.docx; SHA 626cae3d584658842ca36b658e7b5b3cc98d87036747e65b29274f0209734e5a; w:p 148""],""aceitacao"":""Área Reservada ou Dashboard do Investidor: permitir ao investidor acompanhar o estado das submissões, visualizar documentos pendentes, receber notificações, agendar reuniões virtuais, consultar histórico de interacções e solicitar assistência direta junto dos serviços de facilitação do investimento."",""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.",,, MASTER-9dee08d0-0010,"Requisitos mínimos de acessibilidade: O portal deve garantir contraste adequado, leitura simples em dispositivos móveis, navegação por teclado, textos alternativos para imagens e ícones, rótulos claros em formulários e mensagens de erro compreensíveis. Estes requisitos são essenciais para que investidores com diferentes níveis de literacia digital consigam utilizar a plataforma sem apoio constante.",portal do investidor\Reformulacao do portal do investidor.docx; SHA 9dee08d0ebbbb262651a01ffc7e0ade97323664bda59fe8ce0b07ead82736ce1; w:p 129,biblioteca,//; /Simulador; /api/v1/ai/documents; /api/v1/documents/recommendations; /api/v2/investment/admin/legal-sources; /api/v2/investment/admin/legal-sources/{id}/{action}; /apoio; /balcao-virtual; /balcao-virtual/dossiers/{id}/documentos/{document}/conferencia; /como-investir; /documentos; /documentos/{id},"documents, biblioteca, importação autenticada, VerifiedDocumentBinary, jobs/derivadas. 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,Cobrir consulta/importação/doc/derivada/hash/perfil e revogação; preservar original; divulgação separada. Não inferir relação territorial. Critério individual original preservado; cobertura atual ainda não individualizada.,Scanner/worker/GD/WebP e originais privados; ACL por oportunidade/sala diferente de partilha individual não implementada. LegadoOFF. Nenhuma nova decisão identificada para reconciliar/demonstrar esta cláusula. Decisões específicas só após localizar a regra correspondente.,"MASTER-9dee08d0-0010: Requisitos mínimos de acessibilidade: O portal deve garantir contraste adequado, leitura simples em dispositivos móveis, navegação por teclado, textos alternativos para imagens e ícones, rótulos claros em formulários e mensagens de erro compreensíveis. Estes requisitos são essenciais para que investidores com diferentes níveis de literacia digital consigam utilizar a plataforma sem apoio constante.",P2,MASTER-9dee08d0-0010: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas,MASTER-9dee08d0-0010,1,"[{""id"":""MASTER-9dee08d0-0010"",""pedido"":""Requisitos mínimos de acessibilidade: O portal deve garantir contraste adequado, leitura simples em dispositivos móveis, navegação por teclado, textos alternativos para imagens e ícones, rótulos claros em formulários e mensagens de erro compreensíveis. Estes requisitos são essenciais para que investidores com diferentes níveis de literacia digital consigam utilizar a plataforma sem apoio constante."",""fontes"":[""portal do investidor\\Reformulacao do portal do investidor.docx; SHA 9dee08d0ebbbb262651a01ffc7e0ade97323664bda59fe8ce0b07ead82736ce1; w:p 129""],""aceitacao"":""Requisitos mínimos de acessibilidade: O portal deve garantir contraste adequado, leitura simples em dispositivos móveis, navegação por teclado, textos alternativos para imagens e ícones, rótulos claros em formulários e mensagens de erro compreensíveis. Estes requisitos são essenciais para que investidores com diferentes níveis de literacia digital consigam utilizar a plataforma sem apoio constante."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""biblioteca""}]",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.documents: 4 registos; public.portal_resources: 4 registos; public.portal_resource_links: 19 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.",Cobrir consulta/importação/doc/derivada/hash/perfil e revogação; preservar original; divulgação separada. Não inferir relação territorial. Critério individual original preservado; cobertura atual ainda não individualizada.,"documents, biblioteca, importação autenticada, VerifiedDocumentBinary, jobs/derivadas",Scanner/worker/GD/WebP e originais privados; ACL por oportunidade/sala diferente de partilha individual não implementada. LegadoOFF.,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: documentos e legado. 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. | 21/10/2026: pedido expresso da Tempo para uma única navbar, sem retirar acessos; aplicação de AGENTS.md, decisões de 12/10/2026. Agrupamento Tempo, posições, breakpoint 1280px e altura 72px são escolhas de apresentação, não novas regras operacionais. Prova local isolada do header em tools/portal-header-qa.mjs e outputs/header-navbar-20261021/qa-results.json; não encerra o critério integral nem representa implantação no activo.",documentos e legado,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/PortalServicesController.php; app/Jobs/ScanDocumentForMalware.php — componentes do domínio encontrados; correspondência integral deste requisito não presumida,"PortalServicesTest, DocumentLibrarySchemaCompatibilityTest, ImportDocumentBatchTest, ClamDScannerIntegrationTest; 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.",,, MASTER-9dee08d0-0011,A navegação deve reflectir o ciclo de vida do investidor:,portal do investidor\Reformulacao do portal do investidor.docx; SHA 9dee08d0ebbbb262651a01ffc7e0ade97323664bda59fe8ce0b07ead82736ce1; w:p 134 | portal do investidor\backup\Reformulacao do portal do investidor 1.docx; SHA efd550fd2a9d2a21785d7b3c140d4608f1ba27c52f156e2709e9fbf388a1a569; w:p 135 | portal do investidor\backup\Reformulacao do portal do investidor v2.docx; SHA 9f5719c8b0cf78855f727d980b77a8f9409fc7f93e303e25d355d009a07d5734; w:p 148 | portal do investidor\backup\Reformulacao do portal do investidor v3.docx; SHA 626cae3d584658842ca36b658e7b5b3cc98d87036747e65b29274f0209734e5a; w:p 152,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.,"28/10:menus 6e8de43a instalados e delta técnico conferido;conferência visual autenticada pendente;aceitações anteriores preservadas. Histórico: 28/10:reorganização implementada/verificada estruturalmente em QA,sem instalação;validação visual/sessão real pendente;âmbito anterior preservado. Histórico: Sem evidência suficiente | Registo da linha entregue (continuacao): 28/10:revisão de apresentação emQA após relato Tempo;verificação do delta pendente,aceitação anterior preservada no seu âmbito. Histórico: Sem evidência suficiente","28/10:omissões/ocultação corrigidas no código instalado;resta observar a sessão real e responsividade sem presumir perda de permissões. Lacuna anterior: 28/10:omissão de subdestinos e ocultação da área de trabalho corrigidas no candidato QA;falta conferência visual e capacidades reais da sessão,sem presumir perda de permissões. Lacuna histórica: 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. | Registo da linha entregue (continuacao): 28/10:confirmadas omissão de subdestinos na barra lateral e ocultação da área de trabalho no menu da conta;props/acesso da sessão real não demonstrados,sem presumir falta de permissões. Lacuna histórica: 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.",MASTER-9dee08d0-0011: A navegação deve reflectir o ciclo de vida do investidor: | MASTER-efd550fd-0011: A navegação deve reflectir o ciclo de vida do investidor: | MASTER-9f5719c8-0012: A navegação deve reflectir o ciclo de vida do investidor: | MASTER-626cae3d-0009: A navegação deve reflectir o ciclo de vida do investidor:,P2,MASTER-9dee08d0-0011: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-efd550fd-0011: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-9f5719c8-0012: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-626cae3d-0009: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas,MASTER-9dee08d0-0011 | MASTER-efd550fd-0011 | MASTER-9f5719c8-0012 | MASTER-626cae3d-0009,4,"[{""id"":""MASTER-9dee08d0-0011"",""pedido"":""A navegação deve reflectir o ciclo de vida do investidor:"",""fontes"":[""portal do investidor\\Reformulacao do portal do investidor.docx; SHA 9dee08d0ebbbb262651a01ffc7e0ade97323664bda59fe8ce0b07ead82736ce1; w:p 134""],""aceitacao"":""A navegação deve reflectir o ciclo de vida do investidor:"",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""},{""id"":""MASTER-efd550fd-0011"",""pedido"":""A navegação deve reflectir o ciclo de vida do investidor:"",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor 1.docx; SHA efd550fd2a9d2a21785d7b3c140d4608f1ba27c52f156e2709e9fbf388a1a569; w:p 135""],""aceitacao"":""A navegação deve reflectir o ciclo de vida do investidor:"",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""},{""id"":""MASTER-9f5719c8-0012"",""pedido"":""A navegação deve reflectir o ciclo de vida do investidor:"",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor v2.docx; SHA 9f5719c8b0cf78855f727d980b77a8f9409fc7f93e303e25d355d009a07d5734; w:p 148""],""aceitacao"":""A navegação deve reflectir o ciclo de vida do investidor:"",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""},{""id"":""MASTER-626cae3d-0009"",""pedido"":""A navegação deve reflectir o ciclo de vida do investidor:"",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor v3.docx; SHA 626cae3d584658842ca36b658e7b5b3cc98d87036747e65b29274f0209734e5a; w:p 152""],""aceitacao"":""A navegação deve reflectir o ciclo de vida do investidor:"",""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. @@ -419,7 +419,7 @@ MASTER-9dee08d0-0012,"Carteira PII 2026-2030: cada sector deve apresentar oportu MASTER-9dee08d0-0013,"Para transformar o portal num balcão único interoperável, as integrações de sistema devem ser tratadas como uma ambição funcional dependente de acordos institucionais, disponibilidade técnica das entidades públicas e validação jurídica dos fluxos de dados.",portal do investidor\Reformulacao do portal do investidor.docx; SHA 9dee08d0ebbbb262651a01ffc7e0ade97323664bda59fe8ce0b07ead82736ce1; w:p 161,Balcão/orientação,/balcao-virtual/contextos; /balcao-virtual/dossiers; /balcao-virtual/dossiers/{id}; /balcao-virtual/dossiers/{id}/documentos; /balcao-virtual/dossiers/{id}/documentos/{document}; /balcao-virtual/dossiers/{id}/documentos/{document}/conferencia; /balcao-virtual/dossiers/{id}/etapas; /balcao-virtual/dossiers/{id}/fontes; /balcao-virtual/dossiers/{id}/orientacao; /balcao-virtual/dossiers/{id}/pedidos; /balcao-virtual/dossiers/{id}/pressupostos; /balcao-virtual/dossiers/{id}/recomendacoes,"VirtualDesk, pedidos/casos, anexos, contexto dossier e acompanhamento. 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,"Pedido contextual→gravar→submeter→nova sessão→gestor→mensagem/documentos/resultado, reaproveitando A/B. Critério individual original preservado; cobertura atual ainda não individualizada.","Perfis próprios do Balcão; serviços/campos e decisões externas aplicáveis, sem inventar destinatário/prazo. Nenhuma nova decisão identificada para reconciliar/demonstrar esta cláusula. Decisões específicas só após localizar a regra correspondente.","MASTER-9dee08d0-0013: Para transformar o portal num balcão único interoperável, as integrações de sistema devem ser tratadas como uma ambição funcional dependente de acordos institucionais, disponibilidade técnica das entidades públicas e validação jurídica dos fluxos de dados.",P2,MASTER-9dee08d0-0013: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas,MASTER-9dee08d0-0013,1,"[{""id"":""MASTER-9dee08d0-0013"",""pedido"":""Para transformar o portal num balcão único interoperável, as integrações de sistema devem ser tratadas como uma ambição funcional dependente de acordos institucionais, disponibilidade técnica das entidades públicas e validação jurídica dos fluxos de dados."",""fontes"":[""portal do investidor\\Reformulacao do portal do investidor.docx; SHA 9dee08d0ebbbb262651a01ffc7e0ade97323664bda59fe8ce0b07ead82736ce1; w:p 161""],""aceitacao"":""Para transformar o portal num balcão único interoperável, as integrações de sistema devem ser tratadas como uma ambição funcional dependente de acordos institucionais, disponibilidade técnica das entidades públicas e validação jurídica dos fluxos de dados."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""Balcão/orientação""}]",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.desk_cases: 1 registos; public.desk_requests: 0 registos; public.desk_institutional_links: 0 registos; public.desk_messages: 0 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.","Pedido contextual→gravar→submeter→nova sessão→gestor→mensagem/documentos/resultado, reaproveitando A/B. Critério individual original preservado; cobertura atual ainda não individualizada.","VirtualDesk, pedidos/casos, anexos, contexto dossier e acompanhamento","Perfis próprios do Balcão; serviços/campos e decisões externas aplicáveis, sem inventar destinatário/prazo.",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: balcão. 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.,balcão,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/VirtualDesk.php; app/Services/Ai/InvestorGuidance.php — componentes do domínio encontrados; correspondência integral deste requisito não presumida,InvestorGuidanceTest; 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.",,, MASTER-9dee08d0-0014,O conteúdo e os fluxos de trabalho do portal devem refletir rigorosamente o quadro legal vigente em Moçambique:,portal do investidor\Reformulacao do portal do investidor.docx; SHA 9dee08d0ebbbb262651a01ffc7e0ade97323664bda59fe8ce0b07ead82736ce1; w:p 186 | portal do investidor\backup\Reformulacao do portal do investidor 1.docx; SHA efd550fd2a9d2a21785d7b3c140d4608f1ba27c52f156e2709e9fbf388a1a569; w:p 177 | portal do investidor\backup\Reformulacao do portal do investidor v2.docx; SHA 9f5719c8b0cf78855f727d980b77a8f9409fc7f93e303e25d355d009a07d5734; w:p 190 | portal do investidor\backup\Reformulacao do portal do investidor v3.docx; SHA 626cae3d584658842ca36b658e7b5b3cc98d87036747e65b29274f0209734e5a; w:p 194,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.",MASTER-9dee08d0-0014: O conteúdo e os fluxos de trabalho do portal devem refletir rigorosamente o quadro legal vigente em Moçambique: | MASTER-efd550fd-0014: O conteúdo e os fluxos de trabalho do portal devem refletir rigorosamente o quadro legal vigente em Moçambique: | MASTER-9f5719c8-0015: O conteúdo e os fluxos de trabalho do portal devem refletir rigorosamente o quadro legal vigente em Moçambique: | MASTER-626cae3d-0012: O conteúdo e os fluxos de trabalho do portal devem refletir rigorosamente o quadro legal vigente em Moçambique:,P2,MASTER-9dee08d0-0014: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-efd550fd-0014: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-9f5719c8-0015: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-626cae3d-0012: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas,MASTER-9dee08d0-0014 | MASTER-efd550fd-0014 | MASTER-9f5719c8-0015 | MASTER-626cae3d-0012,4,"[{""id"":""MASTER-9dee08d0-0014"",""pedido"":""O conteúdo e os fluxos de trabalho do portal devem refletir rigorosamente o quadro legal vigente em Moçambique:"",""fontes"":[""portal do investidor\\Reformulacao do portal do investidor.docx; SHA 9dee08d0ebbbb262651a01ffc7e0ade97323664bda59fe8ce0b07ead82736ce1; w:p 186""],""aceitacao"":""O conteúdo e os fluxos de trabalho do portal devem refletir rigorosamente o quadro legal vigente em Moçambique:"",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""},{""id"":""MASTER-efd550fd-0014"",""pedido"":""O conteúdo e os fluxos de trabalho do portal devem refletir rigorosamente o quadro legal vigente em Moçambique:"",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor 1.docx; SHA efd550fd2a9d2a21785d7b3c140d4608f1ba27c52f156e2709e9fbf388a1a569; w:p 177""],""aceitacao"":""O conteúdo e os fluxos de trabalho do portal devem refletir rigorosamente o quadro legal vigente em Moçambique:"",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""},{""id"":""MASTER-9f5719c8-0015"",""pedido"":""O conteúdo e os fluxos de trabalho do portal devem refletir rigorosamente o quadro legal vigente em Moçambique:"",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor v2.docx; SHA 9f5719c8b0cf78855f727d980b77a8f9409fc7f93e303e25d355d009a07d5734; w:p 190""],""aceitacao"":""O conteúdo e os fluxos de trabalho do portal devem refletir rigorosamente o quadro legal vigente em Moçambique:"",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""},{""id"":""MASTER-626cae3d-0012"",""pedido"":""O conteúdo e os fluxos de trabalho do portal devem refletir rigorosamente o quadro legal vigente em Moçambique:"",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor v3.docx; SHA 626cae3d584658842ca36b658e7b5b3cc98d87036747e65b29274f0209734e5a; w:p 194""],""aceitacao"":""O conteúdo e os fluxos de trabalho do portal devem refletir rigorosamente o quadro legal vigente em Moçambique:"",""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.",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.",,, MASTER-9dee08d0-0015,"Instruções claras sobre a obrigatoriedade de registo do capital de entrada junto do Banco de Moçambique para assegurar o direito de transferência de lucros, dividendos e capital investido de volta ao país de origem.",portal do investidor\Reformulacao do portal do investidor.docx; SHA 9dee08d0ebbbb262651a01ffc7e0ade97323664bda59fe8ce0b07ead82736ce1; w:p 191,dashboard/perfis,/api/v1/investment/companies/{id?}; /api/v1/investment/notifications; /api/v1/investment/notifications/{id}/read; /api/v1/investment/profile; /api/v1/investment/submissions/{id}/messages; /api/v1/investment/submissions/{id}/revisions/{version}; /dashboard; /investidor/companies/{id?}; /investidor/manifestacoes/{id}/messages; /investidor/manifestacoes/{id}/revisions/{version}; /investidor/notifications; /investidor/notifications/{id}/read,"Fortify, PortalProfileAdministration/Access, grants, policies, portal_security_approvals e menus atuais. 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,"Reutilizar provas existentes; suplementar autorização/revogação/elevação por ação/consumidor, sem sistema paralelo. Critério individual original preservado; cobertura atual ainda não individualizada.","030000→040000 instalados, seis categorias ON; contas/grants reais concluídos. Autorizações por tipo/org e independência mantidas. Nenhuma nova decisão identificada para reconciliar/demonstrar esta cláusula. Decisões específicas só após localizar a regra correspondente.","MASTER-9dee08d0-0015: Instruções claras sobre a obrigatoriedade de registo do capital de entrada junto do Banco de Moçambique para assegurar o direito de transferência de lucros, dividendos e capital investido de volta ao país de origem.",P1,MASTER-9dee08d0-0015: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas,MASTER-9dee08d0-0015,1,"[{""id"":""MASTER-9dee08d0-0015"",""pedido"":""Instruções claras sobre a obrigatoriedade de registo do capital de entrada junto do Banco de Moçambique para assegurar o direito de transferência de lucros, dividendos e capital investido de volta ao país de origem."",""fontes"":[""portal do investidor\\Reformulacao do portal do investidor.docx; SHA 9dee08d0ebbbb262651a01ffc7e0ade97323664bda59fe8ce0b07ead82736ce1; w:p 191""],""aceitacao"":""Instruções claras sobre a obrigatoriedade de registo do capital de entrada junto do Banco de Moçambique para assegurar o direito de transferência de lucros, dividendos e capital investido de volta ao país de origem."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""dashboard/perfis""}]",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.investment_accounts: 0 registos; public.investment_company_declarations: 0 registos; public.investment_submissions: 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.","Reutilizar provas existentes; suplementar autorização/revogação/elevação por ação/consumidor, sem sistema paralelo. Critério individual original preservado; cobertura atual ainda não individualizada.","Fortify, PortalProfileAdministration/Access, grants, policies, portal_security_approvals e menus atuais","030000→040000 instalados, seis categorias ON; contas/grants reais concluídos. Autorizações por tipo/org e independência mantidas.",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: perfis. 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.,perfis,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/DashboardController.php; app/Services/CountryCatalogue.php — componentes do domínio encontrados; correspondência integral deste requisito não presumida,"InvestmentWorkspaceTest, InvestorGuidanceTest; 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.",,, -MASTER-9dee08d0-0016,"Barra de Pesquisa Descontextualizada: O campo de busca deve sugerir temas directamente ligados ao investimento, como “registo de projecto na APIEX”, “benefícios fiscais”, “repatriamento de dividendos”, “Zonas Económicas Especiais” e “licenciamento simplificado”, em vez de exemplos associados a serviços gerais de cidadania.",portal do investidor\Reformulacao do portal do investidor.docx; SHA 9dee08d0ebbbb262651a01ffc7e0ade97323664bda59fe8ce0b07ead82736ce1; w:p 208 | portal do investidor\backup\Reformulacao do portal do investidor 1.docx; SHA efd550fd2a9d2a21785d7b3c140d4608f1ba27c52f156e2709e9fbf388a1a569; w:p 195 | portal do investidor\backup\Reformulacao do portal do investidor v2.docx; SHA 9f5719c8b0cf78855f727d980b77a8f9409fc7f93e303e25d355d009a07d5734; w:p 208 | portal do investidor\backup\Reformulacao do portal do investidor v3.docx; SHA 626cae3d584658842ca36b658e7b5b3cc98d87036747e65b29274f0209734e5a; w:p 213,simuladores,/api/v1/questionnaires/{code}; /api/v1/simulation-drafts; /api/v1/simulation-drafts/claim; /api/v1/simulation-drafts/claim-preview; /api/v1/simulation-drafts/current; /api/v1/simulation-drafts/linkable-businesses; /api/v1/simulation-drafts/{id}; /api/v1/simulation-drafts/{id}/business-link; /api/v1/simulation-drafts/{id}/conversions; /api/v1/simulation-drafts/{id}/touch; /api/v1/simulations; /api/v1/simulations/history,"SimulationExecution, resultados/cenários/fontes, rascunhos e dossier existentes. 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,Cenário→cálculo→gravação→resultado/dossier correto→nova sessão→PDF/derivadas autorizadas. Sem aprovação/score inventado. Critério individual original preservado; cobertura atual ainda não individualizada.,"Regras financeiras/fiscais separadas; fonte oficial aceite, parâmetros/âmbito/período podem faltar; não substituir por0/taxa atual. Nenhuma nova decisão identificada para reconciliar/demonstrar esta cláusula. Decisões específicas só após localizar a regra correspondente.","MASTER-9dee08d0-0016: Barra de Pesquisa Descontextualizada: O campo de busca deve sugerir temas directamente ligados ao investimento, como “registo de projecto na APIEX”, “benefícios fiscais”, “repatriamento de dividendos”, “Zonas Económicas Especiais” e “licenciamento simplificado”, em vez de exemplos associados a serviços gerais de cidadania. | MASTER-efd550fd-0016: Barra de Pesquisa Descontextualizada: O campo de busca deve sugerir temas directamente ligados ao investimento, como “registo de projecto na APIEX”, “benefícios fiscais”, “repatriamento de dividendos”, “Zonas Económicas Especiais” e “licenciamento simplificado”, em vez de exemplos associados a serviços gerais de cidadania. | MASTER-9f5719c8-0017: Barra de Pesquisa Descontextualizada: O campo de busca deve sugerir temas directamente ligados ao investimento, como “registo de projecto na APIEX”, “benefícios fiscais”, “repatriamento de dividendos”, “Zonas Económicas Especiais” e “licenciamento simplificado”, em vez de exemplos associados a serviços gerais de cidadania. | MASTER-626cae3d-0014: Barra de Pesquisa Descontextualizada: O campo de busca deve sugerir temas directamente ligados ao investimento, como “registo de projecto na APIEX”, “benefícios fiscais”, “repatriamento de dividendos”, “Zonas Económicas Especiais” e “licenciamento simplificado”, em vez de exemplos associados a serviços gerais de cidadania.",P2,MASTER-9dee08d0-0016: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-efd550fd-0016: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-9f5719c8-0017: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-626cae3d-0014: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas,MASTER-9dee08d0-0016 | MASTER-efd550fd-0016 | MASTER-9f5719c8-0017 | MASTER-626cae3d-0014,4,"[{""id"":""MASTER-9dee08d0-0016"",""pedido"":""Barra de Pesquisa Descontextualizada: O campo de busca deve sugerir temas directamente ligados ao investimento, como “registo de projecto na APIEX”, “benefícios fiscais”, “repatriamento de dividendos”, “Zonas Económicas Especiais” e “licenciamento simplificado”, em vez de exemplos associados a serviços gerais de cidadania."",""fontes"":[""portal do investidor\\Reformulacao do portal do investidor.docx; SHA 9dee08d0ebbbb262651a01ffc7e0ade97323664bda59fe8ce0b07ead82736ce1; w:p 208""],""aceitacao"":""Barra de Pesquisa Descontextualizada: O campo de busca deve sugerir temas directamente ligados ao investimento, como “registo de projecto na APIEX”, “benefícios fiscais”, “repatriamento de dividendos”, “Zonas Económicas Especiais” e “licenciamento simplificado”, em vez de exemplos associados a serviços gerais de cidadania."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""simuladores""},{""id"":""MASTER-efd550fd-0016"",""pedido"":""Barra de Pesquisa Descontextualizada: O campo de busca deve sugerir temas directamente ligados ao investimento, como “registo de projecto na APIEX”, “benefícios fiscais”, “repatriamento de dividendos”, “Zonas Económicas Especiais” e “licenciamento simplificado”, em vez de exemplos associados a serviços gerais de cidadania."",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor 1.docx; SHA efd550fd2a9d2a21785d7b3c140d4608f1ba27c52f156e2709e9fbf388a1a569; w:p 195""],""aceitacao"":""Barra de Pesquisa Descontextualizada: O campo de busca deve sugerir temas directamente ligados ao investimento, como “registo de projecto na APIEX”, “benefícios fiscais”, “repatriamento de dividendos”, “Zonas Económicas Especiais” e “licenciamento simplificado”, em vez de exemplos associados a serviços gerais de cidadania."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""simuladores""},{""id"":""MASTER-9f5719c8-0017"",""pedido"":""Barra de Pesquisa Descontextualizada: O campo de busca deve sugerir temas directamente ligados ao investimento, como “registo de projecto na APIEX”, “benefícios fiscais”, “repatriamento de dividendos”, “Zonas Económicas Especiais” e “licenciamento simplificado”, em vez de exemplos associados a serviços gerais de cidadania."",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor v2.docx; SHA 9f5719c8b0cf78855f727d980b77a8f9409fc7f93e303e25d355d009a07d5734; w:p 208""],""aceitacao"":""Barra de Pesquisa Descontextualizada: O campo de busca deve sugerir temas directamente ligados ao investimento, como “registo de projecto na APIEX”, “benefícios fiscais”, “repatriamento de dividendos”, “Zonas Económicas Especiais” e “licenciamento simplificado”, em vez de exemplos associados a serviços gerais de cidadania."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""simuladores""},{""id"":""MASTER-626cae3d-0014"",""pedido"":""Barra de Pesquisa Descontextualizada: O campo de busca deve sugerir temas directamente ligados ao investimento, como “registo de projecto na APIEX”, “benefícios fiscais”, “repatriamento de dividendos”, “Zonas Económicas Especiais” e “licenciamento simplificado”, em vez de exemplos associados a serviços gerais de cidadania."",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor v3.docx; SHA 626cae3d584658842ca36b658e7b5b3cc98d87036747e65b29274f0209734e5a; w:p 213""],""aceitacao"":""Barra de Pesquisa Descontextualizada: O campo de busca deve sugerir temas directamente ligados ao investimento, como “registo de projecto na APIEX”, “benefícios fiscais”, “repatriamento de dividendos”, “Zonas Económicas Especiais” e “licenciamento simplificado”, em vez de exemplos associados a serviços gerais de cidadania."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""simuladores""}]",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.simulation_form_drafts: 22 registos; public.simulation_scenarios: 16 registos; public.simulations: 16 registos; public.simulation_tax_rules: 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. | 23/10/2026 — adequação do hero por pedido expresso da Tempo. Aplicadas as orientações permanentes AGENTS.md de 12/10 e 22/10/2026: regras da Tempo, escolhas técnicas identificadas e preservação de dados/histórico. Rotação, WebP e composição são escolhas de implementação. Pesquisa mantém o catálogo existente; não constitui pesquisa global de legislação/licenciamento nem nova restrição organizacional.",Cenário→cálculo→gravação→resultado/dossier correto→nova sessão→PDF/derivadas autorizadas. Sem aprovação/score inventado. Critério individual original preservado; cobertura atual ainda não individualizada.,"SimulationExecution, resultados/cenários/fontes, rascunhos e dossier existentes","Regras financeiras/fiscais separadas; fonte oficial aceite, parâmetros/âmbito/período podem faltar; não substituir por0/taxa atual.",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: simuladores. 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. | 23/10/2026 — adequação do hero por pedido expresso da Tempo: docs/investmoz/public-portal-hero-validation.json; tools/portal-hero-qa.mjs; outputs/portal-hero-20261023/qa-results.json e capturas. Componentes reais com API/navegação sintéticas, sem base activa. Prova limitada ao hero, pesquisa de oportunidades e preservação da entrada documental; critérios mais amplos não encerrados.",simuladores,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/SimulationTaxParameterResolver.php; resources/js/components/simulation-form.tsx — componentes do domínio encontrados; correspondência integral deste requisito não presumida,"SimulationExecutionTest, SimulationFormDraftTest, SimulationDraftContinuityTest, SimulationTaxRuleGovernanceTest; 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.",,, +MASTER-9dee08d0-0016,"Barra de Pesquisa Descontextualizada: O campo de busca deve sugerir temas directamente ligados ao investimento, como “registo de projecto na APIEX”, “benefícios fiscais”, “repatriamento de dividendos”, “Zonas Económicas Especiais” e “licenciamento simplificado”, em vez de exemplos associados a serviços gerais de cidadania.",portal do investidor\Reformulacao do portal do investidor.docx; SHA 9dee08d0ebbbb262651a01ffc7e0ade97323664bda59fe8ce0b07ead82736ce1; w:p 208 | portal do investidor\backup\Reformulacao do portal do investidor 1.docx; SHA efd550fd2a9d2a21785d7b3c140d4608f1ba27c52f156e2709e9fbf388a1a569; w:p 195 | portal do investidor\backup\Reformulacao do portal do investidor v2.docx; SHA 9f5719c8b0cf78855f727d980b77a8f9409fc7f93e303e25d355d009a07d5734; w:p 208 | portal do investidor\backup\Reformulacao do portal do investidor v3.docx; SHA 626cae3d584658842ca36b658e7b5b3cc98d87036747e65b29274f0209734e5a; w:p 213,simuladores,/api/v1/questionnaires/{code}; /api/v1/simulation-drafts; /api/v1/simulation-drafts/claim; /api/v1/simulation-drafts/claim-preview; /api/v1/simulation-drafts/current; /api/v1/simulation-drafts/linkable-businesses; /api/v1/simulation-drafts/{id}; /api/v1/simulation-drafts/{id}/business-link; /api/v1/simulation-drafts/{id}/conversions; /api/v1/simulation-drafts/{id}/touch; /api/v1/simulations; /api/v1/simulations/history,"SimulationExecution, resultados/cenários/fontes, rascunhos e dossier existentes. 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,Cenário→cálculo→gravação→resultado/dossier correto→nova sessão→PDF/derivadas autorizadas. Sem aprovação/score inventado. Critério individual original preservado; cobertura atual ainda não individualizada.,"Regras financeiras/fiscais separadas; fonte oficial aceite, parâmetros/âmbito/período podem faltar; não substituir por0/taxa atual. Nenhuma nova decisão identificada para reconciliar/demonstrar esta cláusula. Decisões específicas só após localizar a regra correspondente.","MASTER-9dee08d0-0016: Barra de Pesquisa Descontextualizada: O campo de busca deve sugerir temas directamente ligados ao investimento, como “registo de projecto na APIEX”, “benefícios fiscais”, “repatriamento de dividendos”, “Zonas Económicas Especiais” e “licenciamento simplificado”, em vez de exemplos associados a serviços gerais de cidadania. | MASTER-efd550fd-0016: Barra de Pesquisa Descontextualizada: O campo de busca deve sugerir temas directamente ligados ao investimento, como “registo de projecto na APIEX”, “benefícios fiscais”, “repatriamento de dividendos”, “Zonas Económicas Especiais” e “licenciamento simplificado”, em vez de exemplos associados a serviços gerais de cidadania. | MASTER-9f5719c8-0017: Barra de Pesquisa Descontextualizada: O campo de busca deve sugerir temas directamente ligados ao investimento, como “registo de projecto na APIEX”, “benefícios fiscais”, “repatriamento de dividendos”, “Zonas Económicas Especiais” e “licenciamento simplificado”, em vez de exemplos associados a serviços gerais de cidadania. | MASTER-626cae3d-0014: Barra de Pesquisa Descontextualizada: O campo de busca deve sugerir temas directamente ligados ao investimento, como “registo de projecto na APIEX”, “benefícios fiscais”, “repatriamento de dividendos”, “Zonas Económicas Especiais” e “licenciamento simplificado”, em vez de exemplos associados a serviços gerais de cidadania.",P2,MASTER-9dee08d0-0016: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-efd550fd-0016: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-9f5719c8-0017: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-626cae3d-0014: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas,MASTER-9dee08d0-0016 | MASTER-efd550fd-0016 | MASTER-9f5719c8-0017 | MASTER-626cae3d-0014,4,"[{""id"":""MASTER-9dee08d0-0016"",""pedido"":""Barra de Pesquisa Descontextualizada: O campo de busca deve sugerir temas directamente ligados ao investimento, como “registo de projecto na APIEX”, “benefícios fiscais”, “repatriamento de dividendos”, “Zonas Económicas Especiais” e “licenciamento simplificado”, em vez de exemplos associados a serviços gerais de cidadania."",""fontes"":[""portal do investidor\\Reformulacao do portal do investidor.docx; SHA 9dee08d0ebbbb262651a01ffc7e0ade97323664bda59fe8ce0b07ead82736ce1; w:p 208""],""aceitacao"":""Barra de Pesquisa Descontextualizada: O campo de busca deve sugerir temas directamente ligados ao investimento, como “registo de projecto na APIEX”, “benefícios fiscais”, “repatriamento de dividendos”, “Zonas Económicas Especiais” e “licenciamento simplificado”, em vez de exemplos associados a serviços gerais de cidadania."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""simuladores""},{""id"":""MASTER-efd550fd-0016"",""pedido"":""Barra de Pesquisa Descontextualizada: O campo de busca deve sugerir temas directamente ligados ao investimento, como “registo de projecto na APIEX”, “benefícios fiscais”, “repatriamento de dividendos”, “Zonas Económicas Especiais” e “licenciamento simplificado”, em vez de exemplos associados a serviços gerais de cidadania."",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor 1.docx; SHA efd550fd2a9d2a21785d7b3c140d4608f1ba27c52f156e2709e9fbf388a1a569; w:p 195""],""aceitacao"":""Barra de Pesquisa Descontextualizada: O campo de busca deve sugerir temas directamente ligados ao investimento, como “registo de projecto na APIEX”, “benefícios fiscais”, “repatriamento de dividendos”, “Zonas Económicas Especiais” e “licenciamento simplificado”, em vez de exemplos associados a serviços gerais de cidadania."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""simuladores""},{""id"":""MASTER-9f5719c8-0017"",""pedido"":""Barra de Pesquisa Descontextualizada: O campo de busca deve sugerir temas directamente ligados ao investimento, como “registo de projecto na APIEX”, “benefícios fiscais”, “repatriamento de dividendos”, “Zonas Económicas Especiais” e “licenciamento simplificado”, em vez de exemplos associados a serviços gerais de cidadania."",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor v2.docx; SHA 9f5719c8b0cf78855f727d980b77a8f9409fc7f93e303e25d355d009a07d5734; w:p 208""],""aceitacao"":""Barra de Pesquisa Descontextualizada: O campo de busca deve sugerir temas directamente ligados ao investimento, como “registo de projecto na APIEX”, “benefícios fiscais”, “repatriamento de dividendos”, “Zonas Económicas Especiais” e “licenciamento simplificado”, em vez de exemplos associados a serviços gerais de cidadania."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""simuladores""},{""id"":""MASTER-626cae3d-0014"",""pedido"":""Barra de Pesquisa Descontextualizada: O campo de busca deve sugerir temas directamente ligados ao investimento, como “registo de projecto na APIEX”, “benefícios fiscais”, “repatriamento de dividendos”, “Zonas Económicas Especiais” e “licenciamento simplificado”, em vez de exemplos associados a serviços gerais de cidadania."",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor v3.docx; SHA 626cae3d584658842ca36b658e7b5b3cc98d87036747e65b29274f0209734e5a; w:p 213""],""aceitacao"":""Barra de Pesquisa Descontextualizada: O campo de busca deve sugerir temas directamente ligados ao investimento, como “registo de projecto na APIEX”, “benefícios fiscais”, “repatriamento de dividendos”, “Zonas Económicas Especiais” e “licenciamento simplificado”, em vez de exemplos associados a serviços gerais de cidadania."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""simuladores""}]",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.simulation_form_drafts: 22 registos; public.simulation_scenarios: 16 registos; public.simulations: 16 registos; public.simulation_tax_rules: 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. | 23/10/2026 — adequação do hero por pedido expresso da Tempo. Aplicadas as orientações permanentes AGENTS.md de 12/10 e 22/10/2026: regras da Tempo, escolhas técnicas identificadas e preservação de dados/histórico. Rotação, WebP e composição são escolhas de implementação. Pesquisa mantém o catálogo existente; não constitui pesquisa global de legislação/licenciamento nem nova restrição organizacional.",Cenário→cálculo→gravação→resultado/dossier correto→nova sessão→PDF/derivadas autorizadas. Sem aprovação/score inventado. Critério individual original preservado; cobertura atual ainda não individualizada.,"SimulationExecution, resultados/cenários/fontes, rascunhos e dossier existentes","Regras financeiras/fiscais separadas; fonte oficial aceite, parâmetros/âmbito/período podem faltar; não substituir por0/taxa atual.",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: simuladores. 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. | 23/10/2026 — adequação do hero por pedido expresso da Tempo: docs/investmoz/public-portal-hero-validation.json; tools/portal-hero-qa.mjs; outputs/portal-hero-20261023/qa-results.json e capturas. Componentes reais com API/navegação sintéticas, sem base activa. Prova limitada ao hero, pesquisa de oportunidades e preservação da entrada documental; critérios mais amplos não encerrados.",simuladores,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/SimulationTaxParameterResolver.php; resources/js/components/simulation-form.tsx — componentes do domínio encontrados; correspondência integral deste requisito não presumida,"SimulationExecutionTest, SimulationFormDraftTest, SimulationDraftContinuityTest, SimulationTaxRuleGovernanceTest; 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.,,," | 03/11/2026: pesquisa rápida local em preparação; páginas e sete grupos públicos, palavras em ordem livre e sem acentos, paginação e aviso de fonte indisponível. Provas isoladas sem BD; consulta real, desempenho, ficheiros, dados privados e cobertura integral pendentes. Não instalada no activo. | 04/11/2026: candidata local com cores Tempo, resultados combinados por relevância e paginação global de cinco, variantes ortográficas e pesquisa de edições editoriais públicas; provas isoladas sem base. Consulta e desempenho com dados reais, ficheiros originais e módulos privados continuam pendentes; sem instalação no activo.","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.",,, MASTER-9dee08d0-0017,"Falta de Profundidade nos Cards: As descrições dos sectores estratégicos devem abandonar frases genéricas e apresentar informação específica de cada área, incluindo oportunidades, localização, tipo de investidor-alvo, principais incentivos, entidades competentes e documentos necessários para iniciar o processo.",portal do investidor\Reformulacao do portal do investidor.docx; SHA 9dee08d0ebbbb262651a01ffc7e0ade97323664bda59fe8ce0b07ead82736ce1; w:p 209 | portal do investidor\backup\Reformulacao do portal do investidor 1.docx; SHA efd550fd2a9d2a21785d7b3c140d4608f1ba27c52f156e2709e9fbf388a1a569; w:p 196 | portal do investidor\backup\Reformulacao do portal do investidor v2.docx; SHA 9f5719c8b0cf78855f727d980b77a8f9409fc7f93e303e25d355d009a07d5734; w:p 209 | portal do investidor\backup\Reformulacao do portal do investidor v3.docx; SHA 626cae3d584658842ca36b658e7b5b3cc98d87036747e65b29274f0209734e5a; w:p 214,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.; SectorInvestment e páginas existentes: conteúdo, oportunidades públicas, associações cadastradas e ações; contexto no mesmo InvestorPath e pré-seleção canónica do setor.","Correcção sectorial669cb897 instalada27/10,validada sobre9fda6d6e;cartões para URLs próprias sem apresentação de todos os detalhes. | Histórico preservado: Disponibilizado em 15/10/2026, setores-fintech-QA-d5b4dc354265 DOC01: 16 fontes e compilação; sete entradas setoriais e especialização Fintech no dossier; três artigos publicados (recursos reais 27/28/29), imagem 24 reutilizada por identidade/hash. Flags existentes preservadas; sem migrations/categorias novas. B e COUNTRY02 preservados.",outputs/investmoz-audit-current-20261012/runtime-active.json; runtime-qa18445.json. Inventário/GET do domínio não demonstra esta cláusula individual.,Implementado parcialmente — entradas setoriais e três artigos ativos,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.","MASTER-9dee08d0-0017: Falta de Profundidade nos Cards: As descrições dos sectores estratégicos devem abandonar frases genéricas e apresentar informação específica de cada área, incluindo oportunidades, localização, tipo de investidor-alvo, principais incentivos, entidades competentes e documentos necessários para iniciar o processo. | MASTER-efd550fd-0017: Falta de Profundidade nos Cards: As descrições dos sectores estratégicos devem abandonar frases genéricas e apresentar informação específica de cada área, incluindo oportunidades, localização, tipo de investidor-alvo, principais incentivos, entidades competentes e documentos necessários para iniciar o processo. | MASTER-9f5719c8-0018: Falta de Profundidade nos Cards: As descrições dos sectores estratégicos devem abandonar frases genéricas e apresentar informação específica de cada área, incluindo oportunidades, localização, tipo de investidor-alvo, principais incentivos, entidades competentes e documentos necessários para iniciar o processo. | MASTER-626cae3d-0015: Falta de Profundidade nos Cards: As descrições dos sectores estratégicos devem abandonar frases genéricas e apresentar informação específica de cada área, incluindo oportunidades, localização, tipo de investidor-alvo, principais incentivos, entidades competentes e documentos necessários para iniciar o processo.",P2,MASTER-9dee08d0-0017: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-efd550fd-0017: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-9f5719c8-0018: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-626cae3d-0015: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas,MASTER-9dee08d0-0017 | MASTER-efd550fd-0017 | MASTER-9f5719c8-0018 | MASTER-626cae3d-0015,4,"[{""id"":""MASTER-9dee08d0-0017"",""pedido"":""Falta de Profundidade nos Cards: As descrições dos sectores estratégicos devem abandonar frases genéricas e apresentar informação específica de cada área, incluindo oportunidades, localização, tipo de investidor-alvo, principais incentivos, entidades competentes e documentos necessários para iniciar o processo."",""fontes"":[""portal do investidor\\Reformulacao do portal do investidor.docx; SHA 9dee08d0ebbbb262651a01ffc7e0ade97323664bda59fe8ce0b07ead82736ce1; w:p 209""],""aceitacao"":""Falta de Profundidade nos Cards: As descrições dos sectores estratégicos devem abandonar frases genéricas e apresentar informação específica de cada área, incluindo oportunidades, localização, tipo de investidor-alvo, principais incentivos, entidades competentes e documentos necessários para iniciar o processo."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""oportunidades""},{""id"":""MASTER-efd550fd-0017"",""pedido"":""Falta de Profundidade nos Cards: As descrições dos sectores estratégicos devem abandonar frases genéricas e apresentar informação específica de cada área, incluindo oportunidades, localização, tipo de investidor-alvo, principais incentivos, entidades competentes e documentos necessários para iniciar o processo."",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor 1.docx; SHA efd550fd2a9d2a21785d7b3c140d4608f1ba27c52f156e2709e9fbf388a1a569; w:p 196""],""aceitacao"":""Falta de Profundidade nos Cards: As descrições dos sectores estratégicos devem abandonar frases genéricas e apresentar informação específica de cada área, incluindo oportunidades, localização, tipo de investidor-alvo, principais incentivos, entidades competentes e documentos necessários para iniciar o processo."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""oportunidades""},{""id"":""MASTER-9f5719c8-0018"",""pedido"":""Falta de Profundidade nos Cards: As descrições dos sectores estratégicos devem abandonar frases genéricas e apresentar informação específica de cada área, incluindo oportunidades, localização, tipo de investidor-alvo, principais incentivos, entidades competentes e documentos necessários para iniciar o processo."",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor v2.docx; SHA 9f5719c8b0cf78855f727d980b77a8f9409fc7f93e303e25d355d009a07d5734; w:p 209""],""aceitacao"":""Falta de Profundidade nos Cards: As descrições dos sectores estratégicos devem abandonar frases genéricas e apresentar informação específica de cada área, incluindo oportunidades, localização, tipo de investidor-alvo, principais incentivos, entidades competentes e documentos necessários para iniciar o processo."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""oportunidades""},{""id"":""MASTER-626cae3d-0015"",""pedido"":""Falta de Profundidade nos Cards: As descrições dos sectores estratégicos devem abandonar frases genéricas e apresentar informação específica de cada área, incluindo oportunidades, localização, tipo de investidor-alvo, principais incentivos, entidades competentes e documentos necessários para iniciar o processo."",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor v3.docx; SHA 626cae3d584658842ca36b658e7b5b3cc98d87036747e65b29274f0209734e5a; w:p 214""],""aceitacao"":""Falta de Profundidade nos Cards: As descrições dos sectores estratégicos devem abandonar frases genéricas e apresentar informação específica de cada área, incluindo oportunidades, localização, tipo de investidor-alvo, principais incentivos, entidades competentes e documentos necessários para iniciar o processo."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""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. | QA 15/10/2026: setores-fintech-QA-d5b4dc354265; sete entradas comerciais/contexto no dossier e três artigos preparados. B/COUNTRY02/aceitações preservados; sem disponibilização ativa. | Disponibilização expressamente autorizada pela Tempo: «Autorizo a disponibilizar». Executada com backup/restauro fresco verificado; apenas conferência operacional, sem repetir aceitação QA. [RECONCILIACAO-43-20261019] 5: Sem saldo de código no recorte visual aceite.","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. | Entrega operacional deste delta concluída. Mercado Financeiro geral vazio e restante acervo mantêm os seus estados. Licenças, requisitos e documentos específicos Fintech por atividade permanecem por determinar; não bloqueiam a preparação independente nem se consideram concluídos. Mantém-se o detalhe integral de incentivos, entidades e documentos específicos por setor por completar. | Recorte QA demonstrado, sem encerrar critérios globais. Histórico económico combinado conservado, fora de novas escolhas; oportunidade6 não inferida como Fintech. Sem fusões por nome. Recuperação existente conferida por leitura e hashes; outra sessão/dispositivo e percursos aceites não reensaiados. Conferência dos anexos pelo utilizador pendente. | Recorte aprovado disponibilizado; requisitos globais permanecem abertos. Atividade historica combinada e oportunidade6 sem reclassificacao. Conferencia dos anexos finais pelo utilizador pendente. Sem repetir testes aceites, formularios ou L1. @@ -969,7 +969,7 @@ MASTER-f70ce307-0002,5.9.1. Documentos obrigatórios e controlos dos campos MIN2 MASTER-51b8f0ad-0002,5.9.1. Documentos obrigatórios e controlos dos campos MIN62,portal do investidor\Portal V4\upgrade do portal de investimentos v2.docx; SHA 51b8f0ad3296d1064a5c12b450874fc72640869f10a164319cd7f3153dd3a086; w:p 72,biblioteca,//; /Simulador; /api/v1/ai/documents; /api/v1/documents/recommendations; /api/v2/investment/admin/legal-sources; /api/v2/investment/admin/legal-sources/{id}/{action}; /apoio; /balcao-virtual; /balcao-virtual/dossiers/{id}/documentos/{document}/conferencia; /como-investir; /documentos; /documentos/{id},"documents, biblioteca, importação autenticada, VerifiedDocumentBinary, jobs/derivadas. 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,Cobrir consulta/importação/doc/derivada/hash/perfil e revogação; preservar original; divulgação separada. Não inferir relação territorial. Critério individual original preservado; cobertura atual ainda não individualizada.,Scanner/worker/GD/WebP e originais privados; ACL por oportunidade/sala diferente de partilha individual não implementada. LegadoOFF. Nenhuma nova decisão identificada para reconciliar/demonstrar esta cláusula. Decisões específicas só após localizar a regra correspondente.,MASTER-51b8f0ad-0002: 5.9.1. Documentos obrigatórios e controlos dos campos MIN62,P2,MASTER-51b8f0ad-0002: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas,MASTER-51b8f0ad-0002,1,"[{""id"":""MASTER-51b8f0ad-0002"",""pedido"":""5.9.1. Documentos obrigatórios e controlos dos campos MIN62"",""fontes"":[""portal do investidor\\Portal V4\\upgrade do portal de investimentos v2.docx; SHA 51b8f0ad3296d1064a5c12b450874fc72640869f10a164319cd7f3153dd3a086; w:p 72""],""aceitacao"":""5.9.1. Documentos obrigatórios e controlos dos campos MIN62"",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""biblioteca""}]",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.documents: 4 registos; public.portal_resources: 4 registos; public.portal_resource_links: 19 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.",Cobrir consulta/importação/doc/derivada/hash/perfil e revogação; preservar original; divulgação separada. Não inferir relação territorial. Critério individual original preservado; cobertura atual ainda não individualizada.,"documents, biblioteca, importação autenticada, VerifiedDocumentBinary, jobs/derivadas",Scanner/worker/GD/WebP e originais privados; ACL por oportunidade/sala diferente de partilha individual não implementada. LegadoOFF.,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: documentos e legado. 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.,documentos e legado,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/PortalServicesController.php; app/Jobs/ScanDocumentForMalware.php — componentes do domínio encontrados; correspondência integral deste requisito não presumida,"PortalServicesTest, DocumentLibrarySchemaCompatibilityTest, ImportDocumentBatchTest, ClamDScannerIntegrationTest; 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.",,, MASTER-efd550fd-0002,"Facilitação e acompanhamento: permitir que os investidores identifiquem os requisitos, as entidades competentes, os documentos necessários, os incentivos aplicáveis e os canais de contacto para cada oportunidade.",portal do investidor\backup\Reformulacao do portal do investidor 1.docx; SHA efd550fd2a9d2a21785d7b3c140d4608f1ba27c52f156e2709e9fbf388a1a569; w:p 98 | portal do investidor\backup\Reformulacao do portal do investidor v2.docx; SHA 9f5719c8b0cf78855f727d980b77a8f9409fc7f93e303e25d355d009a07d5734; w:p 111,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.","MASTER-efd550fd-0002: Facilitação e acompanhamento: permitir que os investidores identifiquem os requisitos, as entidades competentes, os documentos necessários, os incentivos aplicáveis e os canais de contacto para cada oportunidade. | MASTER-9f5719c8-0003: Facilitação e acompanhamento: permitir que os investidores identifiquem os requisitos, as entidades competentes, os documentos necessários, os incentivos aplicáveis e os canais de contacto para cada oportunidade.",P2,MASTER-efd550fd-0002: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-9f5719c8-0003: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas,MASTER-efd550fd-0002 | MASTER-9f5719c8-0003,2,"[{""id"":""MASTER-efd550fd-0002"",""pedido"":""Facilitação e acompanhamento: permitir que os investidores identifiquem os requisitos, as entidades competentes, os documentos necessários, os incentivos aplicáveis e os canais de contacto para cada oportunidade."",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor 1.docx; SHA efd550fd2a9d2a21785d7b3c140d4608f1ba27c52f156e2709e9fbf388a1a569; w:p 98""],""aceitacao"":""Facilitação e acompanhamento: permitir que os investidores identifiquem os requisitos, as entidades competentes, os documentos necessários, os incentivos aplicáveis e os canais de contacto para cada oportunidade."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""oportunidades""},{""id"":""MASTER-9f5719c8-0003"",""pedido"":""Facilitação e acompanhamento: permitir que os investidores identifiquem os requisitos, as entidades competentes, os documentos necessários, os incentivos aplicáveis e os canais de contacto para cada oportunidade."",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor v2.docx; SHA 9f5719c8b0cf78855f727d980b77a8f9409fc7f93e303e25d355d009a07d5734; w:p 111""],""aceitacao"":""Facilitação e acompanhamento: permitir que os investidores identifiquem os requisitos, as entidades competentes, os documentos necessários, os incentivos aplicáveis e os canais de contacto para cada oportunidade."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""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.",,, MASTER-efd550fd-0003,Os eixos estruturantes e os requisitos funcionais que devem estar presentes incluem:,portal do investidor\backup\Reformulacao do portal do investidor 1.docx; SHA efd550fd2a9d2a21785d7b3c140d4608f1ba27c52f156e2709e9fbf388a1a569; w:p 102 | portal do investidor\backup\Reformulacao do portal do investidor v2.docx; SHA 9f5719c8b0cf78855f727d980b77a8f9409fc7f93e303e25d355d009a07d5734; w:p 115 | portal do investidor\backup\Reformulacao do portal do investidor v3.docx; SHA 626cae3d584658842ca36b658e7b5b3cc98d87036747e65b29274f0209734e5a; w:p 124,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.",MASTER-efd550fd-0003: Os eixos estruturantes e os requisitos funcionais que devem estar presentes incluem: | MASTER-9f5719c8-0004: Os eixos estruturantes e os requisitos funcionais que devem estar presentes incluem: | MASTER-626cae3d-0001: Os eixos estruturantes e os requisitos funcionais que devem estar presentes incluem:,P2,MASTER-efd550fd-0003: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-9f5719c8-0004: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-626cae3d-0001: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas,MASTER-efd550fd-0003 | MASTER-9f5719c8-0004 | MASTER-626cae3d-0001,3,"[{""id"":""MASTER-efd550fd-0003"",""pedido"":""Os eixos estruturantes e os requisitos funcionais que devem estar presentes incluem:"",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor 1.docx; SHA efd550fd2a9d2a21785d7b3c140d4608f1ba27c52f156e2709e9fbf388a1a569; w:p 102""],""aceitacao"":""Os eixos estruturantes e os requisitos funcionais que devem estar presentes incluem:"",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""},{""id"":""MASTER-9f5719c8-0004"",""pedido"":""Os eixos estruturantes e os requisitos funcionais que devem estar presentes incluem:"",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor v2.docx; SHA 9f5719c8b0cf78855f727d980b77a8f9409fc7f93e303e25d355d009a07d5734; w:p 115""],""aceitacao"":""Os eixos estruturantes e os requisitos funcionais que devem estar presentes incluem:"",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""governação/outros""},{""id"":""MASTER-626cae3d-0001"",""pedido"":""Os eixos estruturantes e os requisitos funcionais que devem estar presentes incluem:"",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor v3.docx; SHA 626cae3d584658842ca36b658e7b5b3cc98d87036747e65b29274f0209734e5a; w:p 124""],""aceitacao"":""Os eixos estruturantes e os requisitos funcionais que devem estar presentes incluem:"",""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.",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.",,, -MASTER-efd550fd-0008,"Suporte multilíngue: disponibilizar, no mínimo, versões completas em português e inglês, com alternância visível em todas as páginas principais e tradução consistente dos formulários, guias, mensagens de erro, botões de acção e documentos de apoio essenciais. A inclusão futura de francês ou mandarim deve depender dos públicos-alvo prioritários e da capacidade de manter os conteúdos.",portal do investidor\backup\Reformulacao do portal do investidor 1.docx; SHA efd550fd2a9d2a21785d7b3c140d4608f1ba27c52f156e2709e9fbf388a1a569; w:p 130 | portal do investidor\backup\Reformulacao do portal do investidor v2.docx; SHA 9f5719c8b0cf78855f727d980b77a8f9409fc7f93e303e25d355d009a07d5734; w:p 143 | portal do investidor\backup\Reformulacao do portal do investidor v3.docx; SHA 626cae3d584658842ca36b658e7b5b3cc98d87036747e65b29274f0209734e5a; w:p 147,biblioteca,//; /Simulador; /api/v1/ai/documents; /api/v1/documents/recommendations; /api/v2/investment/admin/legal-sources; /api/v2/investment/admin/legal-sources/{id}/{action}; /apoio; /balcao-virtual; /balcao-virtual/dossiers/{id}/documentos/{document}/conferencia; /como-investir; /documentos; /documentos/{id},"documents, biblioteca, importação autenticada, VerifiedDocumentBinary, jobs/derivadas. 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,Cobrir consulta/importação/doc/derivada/hash/perfil e revogação; preservar original; divulgação separada. Não inferir relação territorial. Critério individual original preservado; cobertura atual ainda não individualizada.,Scanner/worker/GD/WebP e originais privados; ACL por oportunidade/sala diferente de partilha individual não implementada. LegadoOFF. Nenhuma nova decisão identificada para reconciliar/demonstrar esta cláusula. Decisões específicas só após localizar a regra correspondente.,"MASTER-efd550fd-0008: Suporte multilíngue: disponibilizar, no mínimo, versões completas em português e inglês, com alternância visível em todas as páginas principais e tradução consistente dos formulários, guias, mensagens de erro, botões de acção e documentos de apoio essenciais. A inclusão futura de francês ou mandarim deve depender dos públicos-alvo prioritários e da capacidade de manter os conteúdos. | MASTER-9f5719c8-0009: Suporte multilíngue: disponibilizar, no mínimo, versões completas em português e inglês, com alternância visível em todas as páginas principais e tradução consistente dos formulários, guias, mensagens de erro, botões de acção e documentos de apoio essenciais. A inclusão futura de francês ou mandarim deve depender dos públicos-alvo prioritários e da capacidade de manter os conteúdos. | MASTER-626cae3d-0006: Suporte multilíngue: disponibilizar, no mínimo, versões completas em português e inglês, com alternância visível em todas as páginas principais e tradução consistente dos formulários, guias, mensagens de erro, botões de acção e documentos de apoio essenciais. A inclusão futura de francês ou mandarim deve depender dos públicos-alvo prioritários e da capacidade de manter os conteúdos.",P2,MASTER-efd550fd-0008: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-9f5719c8-0009: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-626cae3d-0006: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas,MASTER-efd550fd-0008 | MASTER-9f5719c8-0009 | MASTER-626cae3d-0006,3,"[{""id"":""MASTER-efd550fd-0008"",""pedido"":""Suporte multilíngue: disponibilizar, no mínimo, versões completas em português e inglês, com alternância visível em todas as páginas principais e tradução consistente dos formulários, guias, mensagens de erro, botões de acção e documentos de apoio essenciais. A inclusão futura de francês ou mandarim deve depender dos públicos-alvo prioritários e da capacidade de manter os conteúdos."",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor 1.docx; SHA efd550fd2a9d2a21785d7b3c140d4608f1ba27c52f156e2709e9fbf388a1a569; w:p 130""],""aceitacao"":""Suporte multilíngue: disponibilizar, no mínimo, versões completas em português e inglês, com alternância visível em todas as páginas principais e tradução consistente dos formulários, guias, mensagens de erro, botões de acção e documentos de apoio essenciais. A inclusão futura de francês ou mandarim deve depender dos públicos-alvo prioritários e da capacidade de manter os conteúdos."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""biblioteca""},{""id"":""MASTER-9f5719c8-0009"",""pedido"":""Suporte multilíngue: disponibilizar, no mínimo, versões completas em português e inglês, com alternância visível em todas as páginas principais e tradução consistente dos formulários, guias, mensagens de erro, botões de acção e documentos de apoio essenciais. A inclusão futura de francês ou mandarim deve depender dos públicos-alvo prioritários e da capacidade de manter os conteúdos."",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor v2.docx; SHA 9f5719c8b0cf78855f727d980b77a8f9409fc7f93e303e25d355d009a07d5734; w:p 143""],""aceitacao"":""Suporte multilíngue: disponibilizar, no mínimo, versões completas em português e inglês, com alternância visível em todas as páginas principais e tradução consistente dos formulários, guias, mensagens de erro, botões de acção e documentos de apoio essenciais. A inclusão futura de francês ou mandarim deve depender dos públicos-alvo prioritários e da capacidade de manter os conteúdos."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""biblioteca""},{""id"":""MASTER-626cae3d-0006"",""pedido"":""Suporte multilíngue: disponibilizar, no mínimo, versões completas em português e inglês, com alternância visível em todas as páginas principais e tradução consistente dos formulários, guias, mensagens de erro, botões de acção e documentos de apoio essenciais. A inclusão futura de francês ou mandarim deve depender dos públicos-alvo prioritários e da capacidade de manter os conteúdos."",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor v3.docx; SHA 626cae3d584658842ca36b658e7b5b3cc98d87036747e65b29274f0209734e5a; w:p 147""],""aceitacao"":""Suporte multilíngue: disponibilizar, no mínimo, versões completas em português e inglês, com alternância visível em todas as páginas principais e tradução consistente dos formulários, guias, mensagens de erro, botões de acção e documentos de apoio essenciais. A inclusão futura de francês ou mandarim deve depender dos públicos-alvo prioritários e da capacidade de manter os conteúdos."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""biblioteca""}]",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.documents: 4 registos; public.portal_resources: 4 registos; public.portal_resource_links: 19 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.",Cobrir consulta/importação/doc/derivada/hash/perfil e revogação; preservar original; divulgação separada. Não inferir relação territorial. Critério individual original preservado; cobertura atual ainda não individualizada.,"documents, biblioteca, importação autenticada, VerifiedDocumentBinary, jobs/derivadas",Scanner/worker/GD/WebP e originais privados; ACL por oportunidade/sala diferente de partilha individual não implementada. LegadoOFF.,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: documentos e legado. 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.,documentos e legado,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/PortalServicesController.php; app/Jobs/ScanDocumentForMalware.php — componentes do domínio encontrados; correspondência integral deste requisito não presumida,"PortalServicesTest, DocumentLibrarySchemaCompatibilityTest, ImportDocumentBatchTest, ClamDScannerIntegrationTest; 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.",,, +MASTER-efd550fd-0008,"Suporte multilíngue: disponibilizar, no mínimo, versões completas em português e inglês, com alternância visível em todas as páginas principais e tradução consistente dos formulários, guias, mensagens de erro, botões de acção e documentos de apoio essenciais. A inclusão futura de francês ou mandarim deve depender dos públicos-alvo prioritários e da capacidade de manter os conteúdos.",portal do investidor\backup\Reformulacao do portal do investidor 1.docx; SHA efd550fd2a9d2a21785d7b3c140d4608f1ba27c52f156e2709e9fbf388a1a569; w:p 130 | portal do investidor\backup\Reformulacao do portal do investidor v2.docx; SHA 9f5719c8b0cf78855f727d980b77a8f9409fc7f93e303e25d355d009a07d5734; w:p 143 | portal do investidor\backup\Reformulacao do portal do investidor v3.docx; SHA 626cae3d584658842ca36b658e7b5b3cc98d87036747e65b29274f0209734e5a; w:p 147,biblioteca,//; /Simulador; /api/v1/ai/documents; /api/v1/documents/recommendations; /api/v2/investment/admin/legal-sources; /api/v2/investment/admin/legal-sources/{id}/{action}; /apoio; /balcao-virtual; /balcao-virtual/dossiers/{id}/documentos/{document}/conferencia; /como-investir; /documentos; /documentos/{id},"documents, biblioteca, importação autenticada, VerifiedDocumentBinary, jobs/derivadas. Capacidade existente associada ao domínio; correspondência integral desta cláusula só quando demonstrada na prova individual. [02/11/2026: levantamento de codigo e incremento parcial preparados; quatro linguas solicitadas, cobertura transversal pendente. Ver roteiro-manual-qa-i0-i3.md.]","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,Cobrir consulta/importação/doc/derivada/hash/perfil e revogação; preservar original; divulgação separada. Não inferir relação territorial. Critério individual original preservado; cobertura atual ainda não individualizada.,Scanner/worker/GD/WebP e originais privados; ACL por oportunidade/sala diferente de partilha individual não implementada. LegadoOFF. Nenhuma nova decisão identificada para reconciliar/demonstrar esta cláusula. Decisões específicas só após localizar a regra correspondente.,"MASTER-efd550fd-0008: Suporte multilíngue: disponibilizar, no mínimo, versões completas em português e inglês, com alternância visível em todas as páginas principais e tradução consistente dos formulários, guias, mensagens de erro, botões de acção e documentos de apoio essenciais. A inclusão futura de francês ou mandarim deve depender dos públicos-alvo prioritários e da capacidade de manter os conteúdos. | MASTER-9f5719c8-0009: Suporte multilíngue: disponibilizar, no mínimo, versões completas em português e inglês, com alternância visível em todas as páginas principais e tradução consistente dos formulários, guias, mensagens de erro, botões de acção e documentos de apoio essenciais. A inclusão futura de francês ou mandarim deve depender dos públicos-alvo prioritários e da capacidade de manter os conteúdos. | MASTER-626cae3d-0006: Suporte multilíngue: disponibilizar, no mínimo, versões completas em português e inglês, com alternância visível em todas as páginas principais e tradução consistente dos formulários, guias, mensagens de erro, botões de acção e documentos de apoio essenciais. A inclusão futura de francês ou mandarim deve depender dos públicos-alvo prioritários e da capacidade de manter os conteúdos.",P2,MASTER-efd550fd-0008: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-9f5719c8-0009: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-626cae3d-0006: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas,MASTER-efd550fd-0008 | MASTER-9f5719c8-0009 | MASTER-626cae3d-0006,3,"[{""id"":""MASTER-efd550fd-0008"",""pedido"":""Suporte multilíngue: disponibilizar, no mínimo, versões completas em português e inglês, com alternância visível em todas as páginas principais e tradução consistente dos formulários, guias, mensagens de erro, botões de acção e documentos de apoio essenciais. A inclusão futura de francês ou mandarim deve depender dos públicos-alvo prioritários e da capacidade de manter os conteúdos."",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor 1.docx; SHA efd550fd2a9d2a21785d7b3c140d4608f1ba27c52f156e2709e9fbf388a1a569; w:p 130""],""aceitacao"":""Suporte multilíngue: disponibilizar, no mínimo, versões completas em português e inglês, com alternância visível em todas as páginas principais e tradução consistente dos formulários, guias, mensagens de erro, botões de acção e documentos de apoio essenciais. A inclusão futura de francês ou mandarim deve depender dos públicos-alvo prioritários e da capacidade de manter os conteúdos."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""biblioteca""},{""id"":""MASTER-9f5719c8-0009"",""pedido"":""Suporte multilíngue: disponibilizar, no mínimo, versões completas em português e inglês, com alternância visível em todas as páginas principais e tradução consistente dos formulários, guias, mensagens de erro, botões de acção e documentos de apoio essenciais. A inclusão futura de francês ou mandarim deve depender dos públicos-alvo prioritários e da capacidade de manter os conteúdos."",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor v2.docx; SHA 9f5719c8b0cf78855f727d980b77a8f9409fc7f93e303e25d355d009a07d5734; w:p 143""],""aceitacao"":""Suporte multilíngue: disponibilizar, no mínimo, versões completas em português e inglês, com alternância visível em todas as páginas principais e tradução consistente dos formulários, guias, mensagens de erro, botões de acção e documentos de apoio essenciais. A inclusão futura de francês ou mandarim deve depender dos públicos-alvo prioritários e da capacidade de manter os conteúdos."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""biblioteca""},{""id"":""MASTER-626cae3d-0006"",""pedido"":""Suporte multilíngue: disponibilizar, no mínimo, versões completas em português e inglês, com alternância visível em todas as páginas principais e tradução consistente dos formulários, guias, mensagens de erro, botões de acção e documentos de apoio essenciais. A inclusão futura de francês ou mandarim deve depender dos públicos-alvo prioritários e da capacidade de manter os conteúdos."",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor v3.docx; SHA 626cae3d584658842ca36b658e7b5b3cc98d87036747e65b29274f0209734e5a; w:p 147""],""aceitacao"":""Suporte multilíngue: disponibilizar, no mínimo, versões completas em português e inglês, com alternância visível em todas as páginas principais e tradução consistente dos formulários, guias, mensagens de erro, botões de acção e documentos de apoio essenciais. A inclusão futura de francês ou mandarim deve depender dos públicos-alvo prioritários e da capacidade de manter os conteúdos."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""biblioteca""}]",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.documents: 4 registos; public.portal_resources: 4 registos; public.portal_resource_links: 19 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.",Cobrir consulta/importação/doc/derivada/hash/perfil e revogação; preservar original; divulgação separada. Não inferir relação territorial. Critério individual original preservado; cobertura atual ainda não individualizada.,"documents, biblioteca, importação autenticada, VerifiedDocumentBinary, jobs/derivadas",Scanner/worker/GD/WebP e originais privados; ACL por oportunidade/sala diferente de partilha individual não implementada. LegadoOFF.,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: documentos e legado. 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.,documentos e legado,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/PortalServicesController.php; app/Jobs/ScanDocumentForMalware.php — componentes do domínio encontrados; correspondência integral deste requisito não presumida,"PortalServicesTest, DocumentLibrarySchemaCompatibilityTest, ImportDocumentBatchTest, ClamDScannerIntegrationTest; 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.",,, MASTER-efd550fd-0010,"Requisitos mínimos de acessibilidade: O portal deve garantir contraste adequado, leitura simples em dispositivos móveis, navegação por teclado, textos alternativos para imagens e ícones, rótulos claros em formulários e mensagens de erro compreensíveis. Estes requisitos são essenciais para que investidores com diferentes níveis de literacia digital consigam utilizar a plataforma sem apoio contínuo.",portal do investidor\backup\Reformulacao do portal do investidor 1.docx; SHA efd550fd2a9d2a21785d7b3c140d4608f1ba27c52f156e2709e9fbf388a1a569; w:p 132 | portal do investidor\backup\Reformulacao do portal do investidor v2.docx; SHA 9f5719c8b0cf78855f727d980b77a8f9409fc7f93e303e25d355d009a07d5734; w:p 145 | portal do investidor\backup\Reformulacao do portal do investidor v3.docx; SHA 626cae3d584658842ca36b658e7b5b3cc98d87036747e65b29274f0209734e5a; w:p 149,biblioteca,//; /Simulador; /api/v1/ai/documents; /api/v1/documents/recommendations; /api/v2/investment/admin/legal-sources; /api/v2/investment/admin/legal-sources/{id}/{action}; /apoio; /balcao-virtual; /balcao-virtual/dossiers/{id}/documentos/{document}/conferencia; /como-investir; /documentos; /documentos/{id},"documents, biblioteca, importação autenticada, VerifiedDocumentBinary, jobs/derivadas. 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,Cobrir consulta/importação/doc/derivada/hash/perfil e revogação; preservar original; divulgação separada. Não inferir relação territorial. Critério individual original preservado; cobertura atual ainda não individualizada.,Scanner/worker/GD/WebP e originais privados; ACL por oportunidade/sala diferente de partilha individual não implementada. LegadoOFF. Nenhuma nova decisão identificada para reconciliar/demonstrar esta cláusula. Decisões específicas só após localizar a regra correspondente.,"MASTER-efd550fd-0010: Requisitos mínimos de acessibilidade: O portal deve garantir contraste adequado, leitura simples em dispositivos móveis, navegação por teclado, textos alternativos para imagens e ícones, rótulos claros em formulários e mensagens de erro compreensíveis. Estes requisitos são essenciais para que investidores com diferentes níveis de literacia digital consigam utilizar a plataforma sem apoio contínuo. | MASTER-9f5719c8-0011: Requisitos mínimos de acessibilidade: O portal deve garantir contraste adequado, leitura simples em dispositivos móveis, navegação por teclado, textos alternativos para imagens e ícones, rótulos claros em formulários e mensagens de erro compreensíveis. Estes requisitos são essenciais para que investidores com diferentes níveis de literacia digital consigam utilizar a plataforma sem apoio contínuo. | MASTER-626cae3d-0008: Requisitos mínimos de acessibilidade: O portal deve garantir contraste adequado, leitura simples em dispositivos móveis, navegação por teclado, textos alternativos para imagens e ícones, rótulos claros em formulários e mensagens de erro compreensíveis. Estes requisitos são essenciais para que investidores com diferentes níveis de literacia digital consigam utilizar a plataforma sem apoio contínuo.",P2,MASTER-efd550fd-0010: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-9f5719c8-0011: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-626cae3d-0008: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas,MASTER-efd550fd-0010 | MASTER-9f5719c8-0011 | MASTER-626cae3d-0008,3,"[{""id"":""MASTER-efd550fd-0010"",""pedido"":""Requisitos mínimos de acessibilidade: O portal deve garantir contraste adequado, leitura simples em dispositivos móveis, navegação por teclado, textos alternativos para imagens e ícones, rótulos claros em formulários e mensagens de erro compreensíveis. Estes requisitos são essenciais para que investidores com diferentes níveis de literacia digital consigam utilizar a plataforma sem apoio contínuo."",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor 1.docx; SHA efd550fd2a9d2a21785d7b3c140d4608f1ba27c52f156e2709e9fbf388a1a569; w:p 132""],""aceitacao"":""Requisitos mínimos de acessibilidade: O portal deve garantir contraste adequado, leitura simples em dispositivos móveis, navegação por teclado, textos alternativos para imagens e ícones, rótulos claros em formulários e mensagens de erro compreensíveis. Estes requisitos são essenciais para que investidores com diferentes níveis de literacia digital consigam utilizar a plataforma sem apoio contínuo."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""biblioteca""},{""id"":""MASTER-9f5719c8-0011"",""pedido"":""Requisitos mínimos de acessibilidade: O portal deve garantir contraste adequado, leitura simples em dispositivos móveis, navegação por teclado, textos alternativos para imagens e ícones, rótulos claros em formulários e mensagens de erro compreensíveis. Estes requisitos são essenciais para que investidores com diferentes níveis de literacia digital consigam utilizar a plataforma sem apoio contínuo."",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor v2.docx; SHA 9f5719c8b0cf78855f727d980b77a8f9409fc7f93e303e25d355d009a07d5734; w:p 145""],""aceitacao"":""Requisitos mínimos de acessibilidade: O portal deve garantir contraste adequado, leitura simples em dispositivos móveis, navegação por teclado, textos alternativos para imagens e ícones, rótulos claros em formulários e mensagens de erro compreensíveis. Estes requisitos são essenciais para que investidores com diferentes níveis de literacia digital consigam utilizar a plataforma sem apoio contínuo."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""biblioteca""},{""id"":""MASTER-626cae3d-0008"",""pedido"":""Requisitos mínimos de acessibilidade: O portal deve garantir contraste adequado, leitura simples em dispositivos móveis, navegação por teclado, textos alternativos para imagens e ícones, rótulos claros em formulários e mensagens de erro compreensíveis. Estes requisitos são essenciais para que investidores com diferentes níveis de literacia digital consigam utilizar a plataforma sem apoio contínuo."",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor v3.docx; SHA 626cae3d584658842ca36b658e7b5b3cc98d87036747e65b29274f0209734e5a; w:p 149""],""aceitacao"":""Requisitos mínimos de acessibilidade: O portal deve garantir contraste adequado, leitura simples em dispositivos móveis, navegação por teclado, textos alternativos para imagens e ícones, rótulos claros em formulários e mensagens de erro compreensíveis. Estes requisitos são essenciais para que investidores com diferentes níveis de literacia digital consigam utilizar a plataforma sem apoio contínuo."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""biblioteca""}]",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.documents: 4 registos; public.portal_resources: 4 registos; public.portal_resource_links: 19 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.",Cobrir consulta/importação/doc/derivada/hash/perfil e revogação; preservar original; divulgação separada. Não inferir relação territorial. Critério individual original preservado; cobertura atual ainda não individualizada.,"documents, biblioteca, importação autenticada, VerifiedDocumentBinary, jobs/derivadas",Scanner/worker/GD/WebP e originais privados; ACL por oportunidade/sala diferente de partilha individual não implementada. LegadoOFF.,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: documentos e legado. 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.,documentos e legado,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/PortalServicesController.php; app/Jobs/ScanDocumentForMalware.php — componentes do domínio encontrados; correspondência integral deste requisito não presumida,"PortalServicesTest, DocumentLibrarySchemaCompatibilityTest, ImportDocumentBatchTest, ClamDScannerIntegrationTest; 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.",,, MASTER-efd550fd-0012,"d) Carteira PII 2026-2030: cada sector deve apresentar oportunidades associadas ao PII 2026-2030, com indicação da localização, da entidade promotora, da fase de preparação, das necessidades de financiamento, do impacto económico esperado e dos documentos de referência disponíveis.",portal do investidor\backup\Reformulacao do portal do investidor 1.docx; SHA efd550fd2a9d2a21785d7b3c140d4608f1ba27c52f156e2709e9fbf388a1a569; w:p 149 | portal do investidor\backup\Reformulacao do portal do investidor v2.docx; SHA 9f5719c8b0cf78855f727d980b77a8f9409fc7f93e303e25d355d009a07d5734; w:p 162 | portal do investidor\backup\Reformulacao do portal do investidor v3.docx; SHA 626cae3d584658842ca36b658e7b5b3cc98d87036747e65b29274f0209734e5a; w:p 166,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.","MASTER-efd550fd-0012: d) Carteira PII 2026-2030: cada sector deve apresentar oportunidades associadas ao PII 2026-2030, com indicação da localização, da entidade promotora, da fase de preparação, das necessidades de financiamento, do impacto económico esperado e dos documentos de referência disponíveis. | MASTER-9f5719c8-0013: d) Carteira PII 2026-2030: cada sector deve apresentar oportunidades associadas ao PII 2026-2030, com indicação da localização, da entidade promotora, da fase de preparação, das necessidades de financiamento, do impacto económico esperado e dos documentos de referência disponíveis. | MASTER-626cae3d-0010: d) Carteira PII 2026-2030: cada sector deve apresentar oportunidades associadas ao PII 2026-2030, com indicação da localização, da entidade promotora, da fase de preparação, das necessidades de financiamento, do impacto económico esperado e dos documentos de referência disponíveis.",P2,MASTER-efd550fd-0012: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-9f5719c8-0013: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-626cae3d-0010: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas,MASTER-efd550fd-0012 | MASTER-9f5719c8-0013 | MASTER-626cae3d-0010,3,"[{""id"":""MASTER-efd550fd-0012"",""pedido"":""d) Carteira PII 2026-2030: cada sector deve apresentar oportunidades associadas ao PII 2026-2030, com indicação da localização, da entidade promotora, da fase de preparação, das necessidades de financiamento, do impacto económico esperado e dos documentos de referência disponíveis."",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor 1.docx; SHA efd550fd2a9d2a21785d7b3c140d4608f1ba27c52f156e2709e9fbf388a1a569; w:p 149""],""aceitacao"":""d) Carteira PII 2026-2030: cada sector deve apresentar oportunidades associadas ao PII 2026-2030, com indicação da localização, da entidade promotora, da fase de preparação, das necessidades de financiamento, do impacto económico esperado e dos documentos de referência disponíveis."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""oportunidades""},{""id"":""MASTER-9f5719c8-0013"",""pedido"":""d) Carteira PII 2026-2030: cada sector deve apresentar oportunidades associadas ao PII 2026-2030, com indicação da localização, da entidade promotora, da fase de preparação, das necessidades de financiamento, do impacto económico esperado e dos documentos de referência disponíveis."",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor v2.docx; SHA 9f5719c8b0cf78855f727d980b77a8f9409fc7f93e303e25d355d009a07d5734; w:p 162""],""aceitacao"":""d) Carteira PII 2026-2030: cada sector deve apresentar oportunidades associadas ao PII 2026-2030, com indicação da localização, da entidade promotora, da fase de preparação, das necessidades de financiamento, do impacto económico esperado e dos documentos de referência disponíveis."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""oportunidades""},{""id"":""MASTER-626cae3d-0010"",""pedido"":""d) Carteira PII 2026-2030: cada sector deve apresentar oportunidades associadas ao PII 2026-2030, com indicação da localização, da entidade promotora, da fase de preparação, das necessidades de financiamento, do impacto económico esperado e dos documentos de referência disponíveis."",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor v3.docx; SHA 626cae3d584658842ca36b658e7b5b3cc98d87036747e65b29274f0209734e5a; w:p 166""],""aceitacao"":""d) Carteira PII 2026-2030: cada sector deve apresentar oportunidades associadas ao PII 2026-2030, com indicação da localização, da entidade promotora, da fase de preparação, das necessidades de financiamento, do impacto económico esperado e dos documentos de referência disponíveis."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""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.",,, MASTER-efd550fd-0013,"Para transformar o portal num balcão único interoperável, as integrações de sistema devem ser tratadas como uma ambição funcional dependente de acordos institucionais, da disponibilidade técnica das entidades públicas e da validação jurídica dos fluxos de dados.",portal do investidor\backup\Reformulacao do portal do investidor 1.docx; SHA efd550fd2a9d2a21785d7b3c140d4608f1ba27c52f156e2709e9fbf388a1a569; w:p 157 | portal do investidor\backup\Reformulacao do portal do investidor v2.docx; SHA 9f5719c8b0cf78855f727d980b77a8f9409fc7f93e303e25d355d009a07d5734; w:p 170 | portal do investidor\backup\Reformulacao do portal do investidor v3.docx; SHA 626cae3d584658842ca36b658e7b5b3cc98d87036747e65b29274f0209734e5a; w:p 174,Balcão/orientação,/balcao-virtual/contextos; /balcao-virtual/dossiers; /balcao-virtual/dossiers/{id}; /balcao-virtual/dossiers/{id}/documentos; /balcao-virtual/dossiers/{id}/documentos/{document}; /balcao-virtual/dossiers/{id}/documentos/{document}/conferencia; /balcao-virtual/dossiers/{id}/etapas; /balcao-virtual/dossiers/{id}/fontes; /balcao-virtual/dossiers/{id}/orientacao; /balcao-virtual/dossiers/{id}/pedidos; /balcao-virtual/dossiers/{id}/pressupostos; /balcao-virtual/dossiers/{id}/recomendacoes,"VirtualDesk, pedidos/casos, anexos, contexto dossier e acompanhamento. 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,"Pedido contextual→gravar→submeter→nova sessão→gestor→mensagem/documentos/resultado, reaproveitando A/B. Critério individual original preservado; cobertura atual ainda não individualizada.","Perfis próprios do Balcão; serviços/campos e decisões externas aplicáveis, sem inventar destinatário/prazo. Nenhuma nova decisão identificada para reconciliar/demonstrar esta cláusula. Decisões específicas só após localizar a regra correspondente.","MASTER-efd550fd-0013: Para transformar o portal num balcão único interoperável, as integrações de sistema devem ser tratadas como uma ambição funcional dependente de acordos institucionais, da disponibilidade técnica das entidades públicas e da validação jurídica dos fluxos de dados. | MASTER-9f5719c8-0014: Para transformar o portal num balcão único interoperável, as integrações de sistema devem ser tratadas como uma ambição funcional dependente de acordos institucionais, da disponibilidade técnica das entidades públicas e da validação jurídica dos fluxos de dados. | MASTER-626cae3d-0011: Para transformar o portal num balcão único interoperável, as integrações de sistema devem ser tratadas como uma ambição funcional dependente de acordos institucionais, da disponibilidade técnica das entidades públicas e da validação jurídica dos fluxos de dados.",P2,MASTER-efd550fd-0013: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-9f5719c8-0014: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas | MASTER-626cae3d-0011: Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas,MASTER-efd550fd-0013 | MASTER-9f5719c8-0014 | MASTER-626cae3d-0011,3,"[{""id"":""MASTER-efd550fd-0013"",""pedido"":""Para transformar o portal num balcão único interoperável, as integrações de sistema devem ser tratadas como uma ambição funcional dependente de acordos institucionais, da disponibilidade técnica das entidades públicas e da validação jurídica dos fluxos de dados."",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor 1.docx; SHA efd550fd2a9d2a21785d7b3c140d4608f1ba27c52f156e2709e9fbf388a1a569; w:p 157""],""aceitacao"":""Para transformar o portal num balcão único interoperável, as integrações de sistema devem ser tratadas como uma ambição funcional dependente de acordos institucionais, da disponibilidade técnica das entidades públicas e da validação jurídica dos fluxos de dados."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""Balcão/orientação""},{""id"":""MASTER-9f5719c8-0014"",""pedido"":""Para transformar o portal num balcão único interoperável, as integrações de sistema devem ser tratadas como uma ambição funcional dependente de acordos institucionais, da disponibilidade técnica das entidades públicas e da validação jurídica dos fluxos de dados."",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor v2.docx; SHA 9f5719c8b0cf78855f727d980b77a8f9409fc7f93e303e25d355d009a07d5734; w:p 170""],""aceitacao"":""Para transformar o portal num balcão único interoperável, as integrações de sistema devem ser tratadas como uma ambição funcional dependente de acordos institucionais, da disponibilidade técnica das entidades públicas e da validação jurídica dos fluxos de dados."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""Balcão/orientação""},{""id"":""MASTER-626cae3d-0011"",""pedido"":""Para transformar o portal num balcão único interoperável, as integrações de sistema devem ser tratadas como uma ambição funcional dependente de acordos institucionais, da disponibilidade técnica das entidades públicas e da validação jurídica dos fluxos de dados."",""fontes"":[""portal do investidor\\backup\\Reformulacao do portal do investidor v3.docx; SHA 626cae3d584658842ca36b658e7b5b3cc98d87036747e65b29274f0209734e5a; w:p 174""],""aceitacao"":""Para transformar o portal num balcão único interoperável, as integrações de sistema devem ser tratadas como uma ambição funcional dependente de acordos institucionais, da disponibilidade técnica das entidades públicas e da validação jurídica dos fluxos de dados."",""estado"":""parcial"",""variantes"":""Todas as ocorrências exatas preservadas nas fontes; variantes de texto têm linhas distintas"",""modulo"":""Balcão/orientação""}]",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.desk_cases: 1 registos; public.desk_requests: 0 registos; public.desk_institutional_links: 0 registos; public.desk_messages: 0 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.","Pedido contextual→gravar→submeter→nova sessão→gestor→mensagem/documentos/resultado, reaproveitando A/B. Critério individual original preservado; cobertura atual ainda não individualizada.","VirtualDesk, pedidos/casos, anexos, contexto dossier e acompanhamento","Perfis próprios do Balcão; serviços/campos e decisões externas aplicáveis, sem inventar destinatário/prazo.",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: balcão. 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.,balcão,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/VirtualDesk.php; app/Services/Ai/InvestorGuidance.php — componentes do domínio encontrados; correspondência integral deste requisito não presumida,InvestorGuidanceTest; 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.",,, diff --git a/docs/investmoz/v2/auditoria-portal/roteiro-manual-qa-i0-i3.md b/docs/investmoz/v2/auditoria-portal/roteiro-manual-qa-i0-i3.md index e624cdbc..da455a08 100644 --- a/docs/investmoz/v2/auditoria-portal/roteiro-manual-qa-i0-i3.md +++ b/docs/investmoz/v2/auditoria-portal/roteiro-manual-qa-i0-i3.md @@ -1,5 +1,17 @@ # Roteiro executável de interface — I0–I3 QA +## Tradução transversal — levantamento e plano, 02/11/2026 + +O pedido actual determina a tradução nas quatro línguas já apresentadas no selector: português, inglês, francês e espanhol. A referência anterior MASTER-9dee08d0-0008 prevê pelo menos português e inglês; o pedido actual explicita as quatro línguas já expostas. O levantamento é de código, não comprova a situação no ambiente activo nem substitui os recibos de reposição ou a comparação com o último estado demonstrado. + +- Existe um contexto de idioma persistido no navegador e um dicionário em `tempo-brand.tsx`; a sua cobertura anterior concentrava-se no menu e rodapé. A tradução devolve o original quando não encontra uma chave. Os componentes institucionais `Trans` recorrem ao mesmo dicionário, mas várias das suas frases não têm entrada. +- Há 125 ficheiros em `resources/js/pages` (incluindo ficheiros auxiliares): páginas públicas, autenticação, gestão, oportunidades, carteira, percurso, dossier, simulador, terra, parcerias e outras áreas contêm textos portugueses directos. Formulários e componentes partilhados em `resources/js/lib` e `resources/js/components` também contêm rótulos e mensagens directos. A IA possui selector e textos próprios em português/inglês, sem integração completa com o idioma global. Catálogos e questionários de API têm pontos fixados em `pt-MZ`; mensagens de validação Laravel estão em português. Documentos, dados cadastrados, fontes e exportações precisam de tratamento distinto da interface e preservação do original. +- Incremento de código preparado: idioma do documento HTML acompanha o selector; textos comuns do acesso ao conteúdo, caminho de navegação, aviso de homologação, contacto de apoio e selector passam pelo contexto; algumas etiquetas institucionais passam a ter entradas nas três línguas. Os títulos sem entrada continuam no original. Esta cobertura é parcial, não representa conclusão do requisito nem publicação no activo. +- Continuação: etiquetas e placeholders de acesso e registo, apresentação do respectivo título, controlo acessível de visualização da palavra-passe e duas mensagens exactas de autenticação usam o mesmo contexto. Mensagens variáveis, restantes validações, outros ecrãs de autenticação e conteúdos de QA continuam por traduzir. Valores submetidos e comportamento de autenticação não foram alterados. O `types:check` e o build do primeiro incremento passaram; este acréscimo requer nova verificação. +- Comparação com `D:\Projects\revistatempo` e `D:\Projects\Tempo_Investidor\tempo-api`: a Revista usa componentes/hooks explicitamente marcados, POST `/api/translate`, protecção de tokens e cache; a API chama LibreTranslate e devolve o original em caso de falha, inclusive com HTTP 200. Não se demonstrou cobertura integral automática na Revista. Adaptado o cliente para os textos institucionais públicos já marcados com `Trans`, com URL configurável por `VITE_TRANSLATION_API_URL`, preservação de tokens, deduplicação e cache em memória apenas para respostas diferentes do original. Este incremento não envia dados de formulários, dossiers ou outras informações privadas para o serviço. A conectividade real, cobertura restante, resultados e exportações continuam pendentes de verificação e implementação; não declarar a plataforma traduzida. + +Sequência restante: (1) inventariar por rota e componente, comparando com o último estado demonstrado e reutilizando provas e recibos existentes; (2) consolidar catálogo versionado e detecção de chaves ausentes para as quatro línguas; (3) migrar navegação e páginas públicas, autenticação e formulários; (4) migrar oportunidades, carteira, percurso adaptativo, dossier, simulador e áreas privadas/gestão sem alterar regras nem dados; (5) tratar mensagens do servidor, catálogos, conteúdos cadastrados e relatórios, preservando origens e indicando quando não existe versão traduzida; (6) verificar alternância, persistência, regressão dos fluxos, acessibilidade e exportações em QA isolado e registar cobertura por requisito nesta matriz e neste manual. Traduções de documentos oficiais não substituem os originais. + ## C37 — causa confirmada; seleção de conta QA corrigida — P0, visual pendente O pedido de reabertura referia a conta esperada18. A captura manual21:22–21:24UTC demonstra que foi selecionada a conta13 restaurada. O MFA foi alcançado, mas a autenticação nunca se completou. Causa concreta: credenciais MFA da conta13 cifradas pela chave de origem, incompatíveis com a chave estávelQA; a conta18 sintética não foi desafiada nessa tentativa. Nenhuma chave, MFA ou conta13 foi alterada para contornar o problema. diff --git a/resources/js/components/input-error.tsx b/resources/js/components/input-error.tsx index f323cee9..e3ba62b7 100644 --- a/resources/js/components/input-error.tsx +++ b/resources/js/components/input-error.tsx @@ -1,17 +1,19 @@ import type { HTMLAttributes } from 'react'; import { cn } from '@/lib/utils'; +import { useTempoUi } from '@/components/tempo-brand'; export default function InputError({ message, className = '', ...props }: HTMLAttributes & { message?: string }) { + const { t } = useTempoUi(); return message ? (

- {message} + {t(message)}

) : null; } diff --git a/resources/js/components/institutional-source/source-text.tsx b/resources/js/components/institutional-source/source-text.tsx index 4d375db5..ccd33aee 100644 --- a/resources/js/components/institutional-source/source-text.tsx +++ b/resources/js/components/institutional-source/source-text.tsx @@ -1,7 +1,54 @@ +import { useEffect, useState } from 'react'; import { useTempoUi } from '@/components/tempo-brand'; +import { translateText } from '@/translation/service'; +import { protectTranslationTokens, restoreTranslationTokens } from '@/translation/utils'; -// Bridge the original Trans calls to this platform's existing language context. -export function Trans({ children }: { children: string }) { - const { t } = useTempoUi(); - return {t(children)}; +// Only the fixed public institutional texts using this component are sent to Tempo. +const cache = new Map(); +const pending = new Map>(); + +function translateInstitutionalText(text: string, language: string): Promise { + const key = JSON.stringify([language, text]); + const cached = cache.get(key); + if (cached) return Promise.resolve(cached); + const existing = pending.get(key); + if (existing) return existing; + + const protectedText = protectTranslationTokens(text); + const request = translateText({ + text: protectedText.text, + sourceLanguage: 'pt', + targetLanguage: language, + }).then(({ translatedText }) => { + // Do not accept a response that lost an identifier, URL or protected term. + for (const token of protectedText.replacements.keys()) { + if (!translatedText.includes(token)) throw new Error('Translation lost a protected token'); + } + const result = restoreTranslationTokens(translatedText, protectedText.replacements); + // Tempo's API returns the input on upstream failure: do not cache it as success. + if (result !== text) cache.set(key, result); + return result; + }).finally(() => pending.delete(key)); + pending.set(key, request); + return request; +} + +export function Trans({ children }: { children: string }) { + const { language, t } = useTempoUi(); + const localText = t(children); + const [result, setResult] = useState<{ key: string; text: string } | null>(null); + const key = JSON.stringify([language, children]); + + useEffect(() => { + if (language === 'pt' || localText !== children || !children.trim()) return; + let active = true; + translateInstitutionalText(children, language).then((text) => { + if (active) setResult({ key, text }); + }).catch(() => { + if (active) setResult(null); + }); + return () => { active = false; }; + }, [children, key, language, localText]); + + return {result?.key === key ? result.text : localText}; } diff --git a/resources/js/components/password-input.tsx b/resources/js/components/password-input.tsx index e6a9b236..db241de4 100644 --- a/resources/js/components/password-input.tsx +++ b/resources/js/components/password-input.tsx @@ -3,6 +3,7 @@ import type { ComponentProps, Ref } from 'react'; import { useState } from 'react'; import { Input } from '@/components/ui/input'; import { cn } from '@/lib/utils'; +import { useTempoUi } from '@/components/tempo-brand'; export default function PasswordInput({ className, @@ -10,6 +11,7 @@ export default function PasswordInput({ ...props }: Omit, 'type'> & { ref?: Ref }) { const [showPassword, setShowPassword] = useState(false); + const { t } = useTempoUi(); return (
@@ -23,11 +25,7 @@ export default function PasswordInput({ type="button" onClick={() => setShowPassword((prev) => !prev)} className="text-muted-foreground hover:text-foreground focus-visible:ring-ring absolute inset-y-0 right-0 flex items-center rounded-r-md px-3 focus-visible:ring-[3px] focus-visible:outline-none" - aria-label={ - showPassword - ? 'Ocultar palavra-passe' - : 'Mostrar palavra-passe' - } + aria-label={showPassword ? 'Ocultar palavra-passe' : 'Mostrar palavra-passe'} tabIndex={-1} > {showPassword ? ( diff --git a/resources/js/components/portal-hero.tsx b/resources/js/components/portal-hero.tsx index 7f7a8937..400636d5 100644 --- a/resources/js/components/portal-hero.tsx +++ b/resources/js/components/portal-hero.tsx @@ -226,14 +226,14 @@ export default function PortalHero({

{ event.preventDefault(); const value = new FormData(event.currentTarget).get( 'q', ); - router.get('/oportunidades', { + router.get('/pesquisa', { q: typeof value === 'string' ? value.trim() @@ -241,14 +241,14 @@ export default function PortalHero({ }); }} role="search" - aria-label="Pesquisar oportunidades" + aria-label="Pesquisar no InvesteMoz" className="mt-7 w-full max-w-4xl" >
setQuery(event.target.value)} - placeholder="Ex.: agronegócio, Nampula, energia..." + placeholder="Ex.: agronegócio, Nampula, legislação..." className="h-12 w-full min-w-0 flex-1 rounded-xl border-0 bg-white px-4 text-base text-slate-900 outline-none placeholder:text-slate-500" />
@@ -127,15 +129,15 @@ export default function Login({ status, canResetPassword, qaAccount }: Props) { data-test="login-button" > {processing && } - Entrar + {t('Entrar')}
{/* @chisel-registration */}
- Ainda não tem conta?{' '} + {t('Ainda não tem conta?')}{' '} - Criar conta + {t('Criar conta')}
{/* @end-chisel-registration */} diff --git a/resources/js/pages/auth/register.tsx b/resources/js/pages/auth/register.tsx index 409efe2b..75b98678 100644 --- a/resources/js/pages/auth/register.tsx +++ b/resources/js/pages/auth/register.tsx @@ -1,4 +1,5 @@ import { participationIdentity } from '@/lib/participation-identity'; +import { useTempoUi } from '@/components/tempo-brand'; import { Form, Head } from '@inertiajs/react'; import InputError from '@/components/input-error'; import PasswordInput from '@/components/password-input'; @@ -15,9 +16,10 @@ type Props = { }; export default function Register({ passwordRules }: Props) { + const { t } = useTempoUi(); return ( <> - +
- +
- + @@ -76,7 +78,7 @@ export default function Register({ passwordRules }: Props) {
{processing && } - Criar conta + {t('Criar conta')}
- Já tem conta?{' '} + {t('Já tem conta?')}{' '} - Entrar + {t('Entrar')}
diff --git a/resources/js/pages/investment/shared.tsx b/resources/js/pages/investment/shared.tsx index ffcca67a..1d883454 100644 --- a/resources/js/pages/investment/shared.tsx +++ b/resources/js/pages/investment/shared.tsx @@ -1,6 +1,6 @@ import { Head, Link, usePage } from '@inertiajs/react'; import type { ReactNode } from 'react'; -import { PortalMenu } from '@/layouts/portal-navigation'; +import { PortalMenu } from '@/components/portal-menu'; import { cn } from '@/lib/utils'; import type { TerritorialSourceEvidence } from '@/components/territorial-source-evidence'; import type { diff --git a/resources/js/pages/portal/article.tsx b/resources/js/pages/portal/article.tsx index 66d11248..d5f0985b 100644 --- a/resources/js/pages/portal/article.tsx +++ b/resources/js/pages/portal/article.tsx @@ -1,5 +1,5 @@ import { Head, Link } from '@inertiajs/react'; -import { PortalMenu } from '@/layouts/portal-navigation'; +import { PortalMenu } from '@/components/portal-menu'; import EditorialReading, { type EditorialArticle, } from '@/components/editorial-reading'; diff --git a/resources/js/pages/portal/document.tsx b/resources/js/pages/portal/document.tsx index b371ef12..761b95ff 100644 --- a/resources/js/pages/portal/document.tsx +++ b/resources/js/pages/portal/document.tsx @@ -1,5 +1,5 @@ import { Head, Link } from '@inertiajs/react'; -import { PortalMenu } from '@/layouts/portal-navigation'; +import { PortalMenu } from '@/components/portal-menu'; import { PublicDocumentContent, type PublicDocument, diff --git a/resources/js/pages/portal/fiscal-forms.tsx b/resources/js/pages/portal/fiscal-forms.tsx index c10d6a0d..745226cc 100644 --- a/resources/js/pages/portal/fiscal-forms.tsx +++ b/resources/js/pages/portal/fiscal-forms.tsx @@ -1,15 +1,7 @@ import { Head, Link, router } from '@inertiajs/react'; import { useEffect, useState } from 'react'; import { PortalMenu } from '@/layouts/portal-navigation'; -import { - fiscalForms, - fiscalFormFields, - fiscalPreparationIssues, - newFiscalPreparation, - readFiscalPreparation, - type FiscalFormKind, - type FiscalPreparation, -} from '@/lib/fiscal-form-preparation'; +import { fiscalForms, fiscalFormFields, fiscalPreparationIssues, newFiscalPreparation, readFiscalPreparation, type FiscalFormKind, type FiscalPreparation } from '@/lib/fiscal-form-preparation'; const input = 'mt-1 block w-full rounded border border-slate-400 bg-white p-3 text-slate-900'; diff --git a/resources/js/pages/portal/search.tsx b/resources/js/pages/portal/search.tsx new file mode 100644 index 00000000..935defe7 --- /dev/null +++ b/resources/js/pages/portal/search.tsx @@ -0,0 +1,54 @@ +import { Head, Link } from '@inertiajs/react'; +import { useEffect, useState } from 'react'; +import { Search as SearchIcon } from 'lucide-react'; + +type Result = { title: string; description: string | null; url: string; category: string }; +type Results = { data: Result[]; total: number; current_page: number; last_page: number; per_page: number; unavailable: string[] }; + +function pageNumbers(current: number, last: number): (number | string)[] { + const pages = [...new Set([1, last, ...Array.from({ length: 5 }, (_, i) => current - 2 + i)])] + .filter((page) => page >= 1 && page <= last).sort((a, b) => a - b); + return pages.flatMap((page, i) => i > 0 && page - pages[i - 1] > 1 ? [`gap-${page}`, page] : [page]); +} + +export default function Search({ query, results }: { query: string; results: Results }) { + const [input, setInput] = useState(query); + useEffect(() => setInput(query), [query]); + const { data, total, current_page: current, last_page: last, per_page: perPage, unavailable } = results; + const pageUrl = (page: number) => `/pesquisa?${new URLSearchParams({ q: query, page: String(page) })}`; + const start = total ? (current - 1) * perPage + 1 : 0; + const end = Math.min(start + data.length - 1, total); + + return
+ +
+
+

Pesquisa no InvesteMoz

+ + + setInput(event.target.value)} maxLength={120} className="min-h-12 min-w-0 flex-1 rounded-lg border border-slate-300 bg-white px-4 text-base text-[#101820] outline-none focus:ring-2 focus:ring-[#ed1c24]" placeholder="Oportunidades, documentos, sectores..." /> + + +
+
+
+

Pesquise por palavras em qualquer ordem. Pode escrever com ou sem acentos. Os documentos são pesquisados pelos seus dados descritivos.

+ {unavailable.length > 0 &&

Não foi possível consultar: {unavailable.join(', ')}. Os restantes resultados continuam disponíveis; tente novamente mais tarde.

} + {query &&

{total > 0 ? `Resultados ${start}–${end} de ${total}` : 'Nenhum resultado nas fontes consultadas'} para “{query}”.

} + {query && total === 0 &&

Experimente outras palavras ou uma expressão mais curta.

} +
    + {data.map((item) =>
  1. +

    {item.category}

    + {item.title} + {item.description &&

    {item.description}

    } +
  2. )} +
+ {last > 1 && } +
+
; +} diff --git a/resources/js/pages/portal/services.tsx b/resources/js/pages/portal/services.tsx index a3ab064e..9f287808 100644 --- a/resources/js/pages/portal/services.tsx +++ b/resources/js/pages/portal/services.tsx @@ -7,7 +7,7 @@ import { } from '@/components/public-document-content'; import { Head, Link, useForm, usePage } from '@inertiajs/react'; import { useState } from 'react'; -import { PortalMenu } from '@/layouts/portal-navigation'; +import { PortalMenu } from '@/components/portal-menu'; import { newSubmissionKey } from '@/lib/portal-api'; import { ProvinceSelect } from '@/components/investment-territory'; import { SimulationForm } from '@/components/simulation-form'; diff --git a/resources/js/translation/page-translator.tsx b/resources/js/translation/page-translator.tsx new file mode 100644 index 00000000..e317d860 --- /dev/null +++ b/resources/js/translation/page-translator.tsx @@ -0,0 +1,119 @@ +import { useEffect } from 'react'; +import { useTempoUi } from '@/components/tempo-brand'; +import { translateText } from '@/translation/service'; +import { protectTranslationTokens, restoreTranslationTokens } from '@/translation/utils'; + +// Never change the structure of React's DOM or the value of a form control. +const excluded = 'script,style,noscript,code,pre,textarea,[contenteditable]:not([contenteditable="false"]),[translate="no"],[data-no-translate],.no-translate'; +const attributes = ['placeholder', 'title', 'aria-label', 'alt'] as const; +type Target = { node: Text | Element; attribute?: string; original: string; translated?: string }; + +function read(target: Target): string | null { + return target.attribute ? (target.node as Element).getAttribute(target.attribute) : target.node.nodeValue; +} + +function write(target: Target, value: string): void { + if (target.attribute) (target.node as Element).setAttribute(target.attribute, value); + else target.node.nodeValue = value; +} + +export function PageTranslator() { + const { language } = useTempoUi(); + + useEffect(() => { + if (language === 'pt') return; + let active = true; + const abort = new AbortController(); + const targets = new Map>(); + const cache = new Map>(); + const queue: Array<() => void> = []; + let running = 0; + let timer: ReturnType | undefined; + let unavailable = false; + + const run = () => { + if (!active || unavailable || timer || running >= 2 || !queue.length) return; + running++; + queue.shift()?.(); + // Stay below the API's per-minute limit on large pages. + timer = setTimeout(() => { timer = undefined; run(); }, 650); + }; + const translate = (value: string): Promise => { + const previous = cache.get(value); + if (previous) return previous; + const promise = new Promise((resolve, reject) => { + queue.push(() => { + const protectedText = protectTranslationTokens(value); + translateText({ text: protectedText.text, sourceLanguage: 'auto', targetLanguage: language }, abort.signal) + .then(({ translatedText }) => { + for (const token of protectedText.replacements.keys()) { + if (!translatedText.includes(token)) throw new Error('Translation lost a protected token'); + } + resolve(restoreTranslationTokens(translatedText, protectedText.replacements)); + }) + .catch((error) => { + if (!abort.signal.aborted && (error?.status === 503 || error?.status === null)) unavailable = true; + reject(error); + }) + .finally(() => { running--; run(); }); + }); + run(); + }); + cache.set(value, promise); + promise.catch(() => cache.delete(value)); + return promise; + }; + const consider = (node: Text | Element, attribute?: string) => { + const element = node instanceof Element ? node : node.parentElement; + if (!element || element.closest(excluded)) return; + // Without an explicit value, translating an option changes the submitted value. + if (element.closest('option:not([value])')) return; + const key = attribute ?? ''; + const value = attribute ? (node as Element).getAttribute(attribute) : node.nodeValue; + if (!value || value.length > 10000 || !/\p{L}/u.test(value)) return; + let entries = targets.get(node); + if (!entries) { entries = new Map(); targets.set(node, entries); } + const old = entries.get(key); + if (old && (value === old.original || value === old.translated)) return; + const target: Target = { node, attribute, original: value }; + entries.set(key, target); + translate(value).then((translated) => { + if (!active || !node.isConnected || entries?.get(key) !== target || read(target) !== value) return; + target.translated = translated; + write(target, translated); + }).catch(() => { /* Keep the original visible when translation fails. */ }); + }; + const scan = (root: Node) => { + if (root instanceof Text) { consider(root); return; } + if (!(root instanceof Element)) return; + if (root.closest(excluded)) return; + for (const name of attributes) if (root.hasAttribute(name)) consider(root, name); + const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT); + while (walker.nextNode()) { + const node = walker.currentNode; + if (node instanceof Text) consider(node); + else if (node instanceof Element) for (const name of attributes) if (node.hasAttribute(name)) consider(node, name); + } + }; + const observer = new MutationObserver((mutations) => { + for (const mutation of mutations) { + if (mutation.type === 'characterData') scan(mutation.target); + else if (mutation.type === 'attributes' && mutation.target instanceof Element && mutation.attributeName) consider(mutation.target, mutation.attributeName); + else mutation.addedNodes.forEach(scan); + } + }); + observer.observe(document.body, { subtree: true, childList: true, characterData: true, attributes: true, attributeFilter: [...attributes] }); + scan(document.body); + return () => { + active = false; + abort.abort(); + if (timer) clearTimeout(timer); + observer.disconnect(); + for (const entries of targets.values()) for (const target of entries.values()) { + if (target.translated !== undefined && read(target) === target.translated) write(target, target.original); + } + }; + }, [language]); + + return null; +} diff --git a/resources/js/translation/service.ts b/resources/js/translation/service.ts new file mode 100644 index 00000000..cfede32a --- /dev/null +++ b/resources/js/translation/service.ts @@ -0,0 +1,162 @@ +import type { + TranslateRequest, + TranslateResponse, + TranslationLanguage, +} from "./types"; +import { readLanguageList } from "./utils"; + +const translationApiBaseUrl = "/api"; + +const REQUEST_TIMEOUT_MS = 65_000; +const MAX_ATTEMPTS = 3; + +export class TranslationRequestError extends Error { + status: number | null; + retryable: boolean; + + constructor(message: string, options?: { status?: number | null; retryable?: boolean }) { + super(message); + this.name = "TranslationRequestError"; + this.status = options?.status ?? null; + this.retryable = options?.retryable ?? false; + } +} + +function isRetryableStatus(status: number): boolean { + return [429, 500, 502, 503, 504].includes(status); +} + +async function wait(ms: number): Promise { + await new Promise((resolve) => window.setTimeout(resolve, ms)); +} + +async function fetchJson( + input: RequestInfo | URL, + init: RequestInit = {}, + attempt = 1, + maxAttempts = MAX_ATTEMPTS, +): Promise { + const parentSignal = init.signal; + const controller = new AbortController(); + const timeoutId = window.setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS); + + const forwardAbort = () => controller.abort(); + parentSignal?.addEventListener("abort", forwardAbort, { once: true }); + + try { + const response = await fetch(input, { + ...init, + signal: controller.signal, + headers: { + Accept: "application/json", + ...(init.headers ?? {}), + }, + }); + + if (!response.ok) { + const retryable = isRetryableStatus(response.status); + if (retryable && attempt < maxAttempts) { + await wait(300 * attempt); + return fetchJson(input, init, attempt + 1, maxAttempts); + } + + let message = `Translation request failed with status ${response.status}`; + try { + const payload = (await response.json()) as { message?: string }; + if (payload?.message) { + message = payload.message; + } + } catch { + // Ignore JSON parse issues on error responses. + } + + throw new TranslationRequestError(message, { + status: response.status, + retryable, + }); + } + + return (await response.json()) as T; + } catch (error) { + const aborted = controller.signal.aborted; + const isDomAbort = error instanceof DOMException && error.name === "AbortError"; + const parentAborted = parentSignal?.aborted ?? false; + const timedOut = aborted && !parentAborted; + const retryable = timedOut || error instanceof TypeError; + + if (retryable && attempt < maxAttempts) { + await wait(300 * attempt); + return fetchJson(input, init, attempt + 1, maxAttempts); + } + + if (error instanceof TranslationRequestError) { + throw error; + } + + if (parentAborted || isDomAbort) { + throw error; + } + + if (timedOut) { + throw new TranslationRequestError("Translation request timed out", { + status: null, + retryable, + }); + } + + throw new TranslationRequestError("Translation request failed", { + status: null, + retryable, + }); + } finally { + window.clearTimeout(timeoutId); + parentSignal?.removeEventListener("abort", forwardAbort); + } +} + +export async function getLanguages(signal?: AbortSignal): Promise { + const payload = await fetchJson(`${translationApiBaseUrl}/translate/languages`, { + method: "GET", + signal, + }, 1, 1); + + return readLanguageList(payload); +} + +export async function translateText( + request: TranslateRequest, + signal?: AbortSignal, +): Promise { + const payload = await fetchJson<{ + original_text?: string; + translated_text?: string; + source_lang?: string; + target_lang?: string; + }>(`${translationApiBaseUrl}/translate`, { + method: "POST", + signal, + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + text: request.text, + source_lang: request.sourceLanguage, + target_lang: request.targetLanguage, + is_html: request.isHtml ?? false, + }), + }, 1, 1); + + if (typeof payload.translated_text !== "string" || payload.translated_text.trim() === "") { + throw new TranslationRequestError("Translation response is missing translated_text", { + status: 200, + retryable: false, + }); + } + + return { + originalText: payload.original_text ?? request.text, + translatedText: payload.translated_text, + sourceLanguage: payload.source_lang ?? request.sourceLanguage, + targetLanguage: payload.target_lang ?? request.targetLanguage, + }; +} diff --git a/resources/js/translation/types.ts b/resources/js/translation/types.ts new file mode 100644 index 00000000..20bd534d --- /dev/null +++ b/resources/js/translation/types.ts @@ -0,0 +1,41 @@ +export interface TranslationLanguage { + code: string; + name: string; + targets?: string[]; +} + +export interface TranslateRequest { + text: string; + sourceLanguage: string; + targetLanguage: string; + isHtml?: boolean; +} + +export interface TranslateResponse { + originalText: string; + translatedText: string; + sourceLanguage: string; + targetLanguage: string; +} + +export interface TranslationCacheEntry { + value: string; + createdAt: number; + expiresAt: number; +} + +export interface ProtectedTranslationTokens { + text: string; + replacements: Map; +} + +export interface TranslationPayloadHealth { + shouldTranslate: boolean; + normalizedText: string; +} + +export interface TranslateOptions { + isHtml?: boolean; + signal?: AbortSignal; + protectedTerms?: string[]; +} diff --git a/resources/js/translation/utils.ts b/resources/js/translation/utils.ts new file mode 100644 index 00000000..02c6c951 --- /dev/null +++ b/resources/js/translation/utils.ts @@ -0,0 +1,615 @@ +import type { + ProtectedTranslationTokens, + TranslationPayloadHealth, + TranslationCacheEntry, + TranslationLanguage, +} from "./types"; + +const CACHE_PREFIX = "tempo_translation_v1"; +const CACHE_VERSION = "v1"; +const CACHE_TTL = 7 * 24 * 60 * 60 * 1000; +const CACHE_MAX_ENTRIES = 200; +const CACHE_CLEANUP_INTERVAL = 12 * 60 * 60 * 1000; +const CLEANUP_MARKER_KEY = `${CACHE_PREFIX}:cleanup`; +const memoryCache = new Map(); + +const LANGUAGE_ALIASES: Record = { + ar: "ar", + "ar-sa": "ar", + en: "en", + "en-gb": "en", + "en-us": "en", + es: "es", + "es-es": "es", + fr: "fr", + "fr-fr": "fr", + hi: "hi", + "hi-in": "hi", + pt: "pt", + "pt-mz": "pt", + "pt-pt": "pt", + zh: "zh-Hans", + "zh-cn": "zh-Hans", + "zh-hans": "zh-Hans", +}; + +const apiUnsafeNodes = "script, iframe, object, embed, link, meta"; +const excludedTranslationNodes = + "code, pre, script, style, textarea, noscript, .no-translate, [data-no-translate], [translate='no']"; +const INTERNAL_PLACEHOLDER_PREFIX = "¤TRX"; +const BATCH_MAX_ITEMS = 8; +const BATCH_MAX_CHARS = 2200; +const TRANSLATION_PLACEHOLDER_PATTERN = /¤TRX[A-Z]+\d+¤/g; +const HTML_BATCH_ROOT_ATTR = "data-trx-batch-root"; +const HTML_BATCH_NODE_ATTR = "data-trx-node"; + +export const fallbackTranslationLanguages: TranslationLanguage[] = [ + { code: "pt", name: "Português" }, + { code: "en", name: "English" }, + { code: "fr", name: "French" }, + { code: "es", name: "Spanish" }, + { code: "ar", name: "Arabic" }, + { code: "hi", name: "Hindi" }, + { code: "zh-Hans", name: "Chinese" }, +]; + +export const DEFAULT_PROTECTED_TERMS = [ + "Revista TEMPO", + "Grupo TEMPO", + "Tempo TV", + "Tempo Connect", + "Informação Pública", + "InvesteMoz", + "TEMPO", +] as const; + +function getStorage(): Storage | null { + if (typeof window === "undefined") return null; + return window.localStorage; +} + +function normalizeAlias(code: string): string { + return LANGUAGE_ALIASES[code.trim().toLowerCase()] ?? code.trim(); +} + +export function normalizeLanguageCode( + code: string, + availableLanguages: TranslationLanguage[], +): string | null { + if (!code) return null; + + const normalized = normalizeAlias(code); + const supportedCodes = new Set( + availableLanguages.map((language) => normalizeAlias(language.code)), + ); + + return supportedCodes.has(normalized) ? normalized : null; +} + +export function getBrowserLanguageCode(code: string): string { + switch (code) { + case "zh-Hans": + return "zh-CN"; + default: + return code; + } +} + +export function getLocaleForLanguage(code: string): string { + switch (normalizeAlias(code)) { + case "pt": + return "pt-MZ"; + case "zh-Hans": + return "zh-CN"; + default: + return normalizeAlias(code); + } +} + +export function isRtlLanguage(code: string): boolean { + return normalizeAlias(code) === "ar"; +} + +export function readLanguageList(payload: unknown): TranslationLanguage[] { + const source = Array.isArray(payload) + ? payload + : Array.isArray((payload as { value?: unknown[] })?.value) + ? (payload as { value: unknown[] }).value + : Array.isArray((payload as { data?: unknown[] })?.data) + ? (payload as { data: unknown[] }).data + : []; + + return source + .filter((item): item is TranslationLanguage => { + return ( + Boolean(item) && + typeof item === "object" && + typeof item.code === "string" && + typeof item.name === "string" + ); + }) + .map((language) => ({ + code: normalizeAlias(language.code), + name: language.name, + targets: Array.isArray(language.targets) + ? language.targets.map((target) => normalizeAlias(target)) + : undefined, + })); +} + +export function withPortugueseFirst( + languages: TranslationLanguage[], +): TranslationLanguage[] { + const unique = new Map(); + + for (const language of languages) { + const code = normalizeAlias(language.code); + if (!unique.has(code)) { + unique.set(code, { + ...language, + code, + name: code === "pt" ? "Português" : language.name, + }); + } + } + + const items = Array.from(unique.values()).sort((a, b) => + a.name.localeCompare(b.name), + ); + + const portuguese = + items.find((language) => language.code === "pt") ?? fallbackTranslationLanguages[0]; + + return [portuguese, ...items.filter((language) => language.code !== "pt")]; +} + +export function hashTranslationText(value: string): string { + let hash = 2166136261; + + for (let index = 0; index < value.length; index += 1) { + hash ^= value.charCodeAt(index); + hash = Math.imul(hash, 16777619); + } + + return (hash >>> 0).toString(16); +} + +export function createTranslationCacheKey(params: { + sourceLanguage: string; + targetLanguage: string; + isHtml: boolean; + text: string; + protectedTerms?: string[]; +}): string { + return [ + CACHE_PREFIX, + CACHE_VERSION, + params.sourceLanguage, + params.targetLanguage, + params.isHtml ? "html" : "text", + hashTranslationText(params.text), + hashTranslationText( + JSON.stringify( + collectProtectedTerms(params.protectedTerms).map((term) => term.toLowerCase()), + ), + ), + ].join(":"); +} + +export function getMemoryCacheEntry(key: string): TranslationCacheEntry | null { + const entry = memoryCache.get(key); + if (!entry) return null; + + if (entry.expiresAt <= Date.now()) { + memoryCache.delete(key); + return null; + } + + return entry; +} + +export function setMemoryCacheEntry(key: string, value: string): TranslationCacheEntry { + const entry: TranslationCacheEntry = { + value, + createdAt: Date.now(), + expiresAt: Date.now() + CACHE_TTL, + }; + + memoryCache.set(key, entry); + return entry; +} + +export function getPersistentCacheEntry(key: string): TranslationCacheEntry | null { + const storage = getStorage(); + if (!storage) return null; + + const raw = storage.getItem(key); + if (!raw) return null; + + try { + const entry = JSON.parse(raw) as TranslationCacheEntry; + if (entry.expiresAt <= Date.now()) { + storage.removeItem(key); + return null; + } + + return entry; + } catch { + storage.removeItem(key); + return null; + } +} + +export function setPersistentCacheEntry(key: string, entry: TranslationCacheEntry): void { + const storage = getStorage(); + if (!storage) return; + + storage.setItem(key, JSON.stringify(entry)); +} + +export function cleanupTranslationCache(): void { + const storage = getStorage(); + if (!storage) return; + + const lastCleanup = Number(storage.getItem(CLEANUP_MARKER_KEY) ?? "0"); + if (lastCleanup && Date.now() - lastCleanup < CACHE_CLEANUP_INTERVAL) { + return; + } + + const translationKeys: string[] = []; + + for (let index = 0; index < storage.length; index += 1) { + const key = storage.key(index); + if (key && key.startsWith(`${CACHE_PREFIX}:${CACHE_VERSION}:`)) { + translationKeys.push(key); + } + } + + const validEntries: Array<{ key: string; entry: TranslationCacheEntry }> = []; + + for (const key of translationKeys) { + const entry = getPersistentCacheEntry(key); + if (entry) { + validEntries.push({ key, entry }); + } + } + + validEntries + .sort((a, b) => a.entry.createdAt - b.entry.createdAt) + .slice(0, Math.max(0, validEntries.length - CACHE_MAX_ENTRIES)) + .forEach(({ key }) => storage.removeItem(key)); + + storage.setItem(CLEANUP_MARKER_KEY, String(Date.now())); +} + +function createPlaceholder(prefix: string, index: number): string { + return `${INTERNAL_PLACEHOLDER_PREFIX}${prefix}${index}¤`; +} + +function escapeRegExp(value: string): string { + return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} + +export function collectProtectedTerms(terms?: Array): string[] { + const combined = [...DEFAULT_PROTECTED_TERMS, ...(terms ?? [])] + .map((term) => String(term ?? "").trim()) + .filter(Boolean); + + return Array.from(new Set(combined)).sort((a, b) => b.length - a.length); +} + +function protectMatches( + input: string, + pattern: RegExp, + replacements: Map, + prefix: string, +): string { + return input.replace(pattern, (match) => { + const placeholder = createPlaceholder(prefix, replacements.size); + replacements.set(placeholder, match); + return placeholder; + }); +} + +function protectExcludedHtmlBlocks( + input: string, + replacements: Map, +): string { + if (typeof window === "undefined") return input; + + const documentFragment = new DOMParser().parseFromString(input, "text/html"); + const nodes = Array.from(documentFragment.body.querySelectorAll(excludedTranslationNodes)); + + nodes.forEach((node) => { + const placeholder = createPlaceholder("HTML", replacements.size); + replacements.set(placeholder, node.outerHTML); + node.replaceWith(documentFragment.createTextNode(placeholder)); + }); + + return documentFragment.body.innerHTML; +} + +export function protectTranslationTokens( + input: string, + options?: { isHtml?: boolean; protectedTerms?: string[] }, +): ProtectedTranslationTokens { + const replacements = new Map(); + let output = String(input ?? ""); + + if (options?.isHtml) { + output = protectExcludedHtmlBlocks(output, replacements); + } + + output = protectMatches( + output, + /https?:\/\/[^\s"'<>]+/gi, + replacements, + "URL", + ); + output = protectMatches( + output, + /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi, + replacements, + "EMAIL", + ); + output = protectMatches( + output, + /\{\{[^{}]+\}\}|\{[a-zA-Z0-9_.-]+\}|%(\d+\$)?[sd]|:[a-zA-Z_][a-zA-Z0-9_]*/g, + replacements, + "TOKEN", + ); + + for (const term of collectProtectedTerms(options?.protectedTerms)) { + const escapedTerm = escapeRegExp(term); + const pattern = new RegExp(`(^|[^\\p{L}\\p{N}])(${escapedTerm})(?=$|[^\\p{L}\\p{N}])`, "giu"); + + output = output.replace(pattern, (match, prefix, protectedValue) => { + const placeholder = createPlaceholder("TERM", replacements.size); + replacements.set(placeholder, protectedValue); + return `${prefix}${placeholder}`; + }); + } + + return { text: output, replacements }; +} + +export function restoreTranslationTokens( + input: string, + replacements: Map, +): string { + let output = String(input ?? ""); + + for (const [placeholder, original] of replacements.entries()) { + output = output.split(placeholder).join(original); + } + + return output; +} + +export function getTranslationPayloadHealth( + input: string, + options?: { isHtml?: boolean; protectedTerms?: string[] }, +): TranslationPayloadHealth { + const source = String(input ?? ""); + if (!source.trim()) { + return { + shouldTranslate: false, + normalizedText: source, + }; + } + + if (options?.isHtml && typeof window !== "undefined") { + const documentFragment = new DOMParser().parseFromString(source, "text/html"); + const walker = document.createTreeWalker( + documentFragment.body, + NodeFilter.SHOW_TEXT, + { + acceptNode(node) { + return isSkippableTextNode(node as Text) + ? NodeFilter.FILTER_REJECT + : NodeFilter.FILTER_ACCEPT; + }, + }, + ); + + if (!walker.nextNode()) { + return { + shouldTranslate: false, + normalizedText: source, + }; + } + } + + const protectedPayload = protectTranslationTokens(source, options); + const remainder = protectedPayload.text + .replace(TRANSLATION_PLACEHOLDER_PATTERN, "") + .trim(); + + if (!remainder) { + return { + shouldTranslate: false, + normalizedText: source, + }; + } + + const hasLetters = /[\p{L}]/u.test(remainder); + if (!hasLetters) { + return { + shouldTranslate: false, + normalizedText: source, + }; + } + + return { + shouldTranslate: true, + normalizedText: source, + }; +} + +export function sanitizeTranslatedHtml(html: string): string { + if (typeof window === "undefined" || !html) return ""; + + const documentFragment = new DOMParser().parseFromString(String(html), "text/html"); + documentFragment.querySelectorAll(apiUnsafeNodes).forEach((node) => node.remove()); + + documentFragment.querySelectorAll("*").forEach((node) => { + for (const attribute of Array.from(node.attributes)) { + const name = attribute.name.toLowerCase(); + const value = attribute.value.trim().toLowerCase(); + + if ( + name.startsWith("on") || + value.startsWith("javascript:") || + value.startsWith("data:text/html") || + value.startsWith("vbscript:") + ) { + node.removeAttribute(attribute.name); + } + } + }); + + return documentFragment.body.innerHTML; +} + +function isSkippableTextNode(node: Text): boolean { + const parent = node.parentElement; + const value = node.textContent ?? ""; + + if (!value.trim()) return true; + if (!parent) return true; + if (parent.closest(excludedTranslationNodes)) return true; + + return false; +} + +function createBatchPayload(values: string[]): { + payload: string; +} { + if (typeof window === "undefined") { + return { + payload: values.join(" "), + }; + } + + const documentFragment = document.implementation.createHTMLDocument(""); + const root = documentFragment.createElement("div"); + root.setAttribute(HTML_BATCH_ROOT_ATTR, "1"); + + values.forEach((value, index) => { + const node = documentFragment.createElement("span"); + node.setAttribute(HTML_BATCH_NODE_ATTR, String(index)); + node.textContent = value; + root.appendChild(node); + }); + + documentFragment.body.appendChild(root); + + return { payload: root.outerHTML }; +} + +function parseBatchResponse( + translated: string, + originalValues: string[], +): string[] { + if (typeof window === "undefined") { + return originalValues; + } + + const documentFragment = new DOMParser().parseFromString(translated, "text/html"); + const root = documentFragment.body.querySelector(`[${HTML_BATCH_ROOT_ATTR}]`); + if (!root) { + return originalValues; + } + + const nodes = Array.from(root.querySelectorAll(`[${HTML_BATCH_NODE_ATTR}]`)); + if (nodes.length !== originalValues.length) { + return originalValues; + } + + return originalValues.map((originalValue, index) => { + const node = nodes.find( + (candidate) => candidate.getAttribute(HTML_BATCH_NODE_ATTR) === String(index), + ); + + return node?.textContent?.trim() ? node.textContent : originalValue; + }); +} + +function buildTextBatches(nodes: Text[]): Text[][] { + const batches: Text[][] = []; + let currentBatch: Text[] = []; + let currentLength = 0; + + for (const node of nodes) { + const textLength = (node.textContent ?? "").length; + const shouldFlush = + currentBatch.length >= BATCH_MAX_ITEMS || + currentLength + textLength > BATCH_MAX_CHARS; + + if (shouldFlush && currentBatch.length > 0) { + batches.push(currentBatch); + currentBatch = []; + currentLength = 0; + } + + currentBatch.push(node); + currentLength += textLength; + } + + if (currentBatch.length > 0) { + batches.push(currentBatch); + } + + return batches; +} + +export async function translateHtmlByTextNodes(params: { + html: string; + protectedTerms?: string[]; + translateText: (text: string, options?: { protectedTerms?: string[]; isHtml?: boolean }) => Promise; +}): Promise { + if (typeof window === "undefined" || !params.html.trim()) { + return params.html; + } + + const documentFragment = new DOMParser().parseFromString(params.html, "text/html"); + const walker = document.createTreeWalker( + documentFragment.body, + NodeFilter.SHOW_TEXT, + { + acceptNode(node) { + return isSkippableTextNode(node as Text) + ? NodeFilter.FILTER_REJECT + : NodeFilter.FILTER_ACCEPT; + }, + }, + ); + + const textNodes: Text[] = []; + let currentNode = walker.nextNode(); + while (currentNode) { + textNodes.push(currentNode as Text); + currentNode = walker.nextNode(); + } + + if (textNodes.length === 0) { + return sanitizeTranslatedHtml(documentFragment.body.innerHTML); + } + + for (const batch of buildTextBatches(textNodes)) { + const originalValues = batch.map((node) => node.textContent ?? ""); + const { payload } = createBatchPayload(originalValues); + const translatedPayload = await params.translateText(payload, { + protectedTerms: params.protectedTerms, + isHtml: true, + }); + + const translatedValues = parseBatchResponse(translatedPayload, originalValues); + + batch.forEach((node, index) => { + node.textContent = translatedValues[index]; + }); + } + + return sanitizeTranslatedHtml(documentFragment.body.innerHTML); +} diff --git a/routes/api.php b/routes/api.php index 5278aca3..07b49d34 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,5 +1,6 @@ middleware('throttle:60,1'); +Route::post('translate', [TranslationApiController::class, 'translate'])->middleware('throttle:120,1'); + Route::prefix('v1')->middleware(['throttle:api', CorrelationId::class])->group(function () { Route::get('/catalogs/{catalog}', [ReferenceCatalogController::class, 'index']) ->whereIn('catalog', ['authorities', 'jurisdictions', 'economic-activities', 'countries']); diff --git a/routes/web.php b/routes/web.php index 0ecbc5b0..02af514b 100644 --- a/routes/web.php +++ b/routes/web.php @@ -114,7 +114,8 @@ Route::middleware(['auth', 'verified', EnsureDocumentManagement::class])->group( Route::get('/artigos/{id}', [EditorialArticleController::class, 'show'])->whereNumber('id')->name('editorial.show'); Route::get('/carteira', [InvestmentPortfolioController::class, 'index'])->name('portfolio.index'); Route::get('/carteira/{id}', [InvestmentPortfolioController::class, 'show'])->whereUuid('id')->name('portfolio.show'); -Route::get('/carteira/{id}/interesse', [InvestmentPortfolioController::class, 'resumeInterest'])->whereUuid('id')->middleware(['auth', VerifiedOrSimpleParticipation::class])->name('portfolio.interest'); +Route::get('/carteira/{id}/interesse', [InvestmentPortfolioController::class, 'resumeInterest'])->whereUuid('id')->middleware(['auth', \App\Http\Middleware\VerifiedOrSimpleParticipation::class])->name('portfolio.interest'); +Route::get('/pesquisa', \App\Http\Controllers\GlobalSearchController::class)->name('portal.search'); Route::get('/oportunidades', [InvestmentDiscoveryController::class, 'index'])->name('portal.opportunities'); Route::get('/oportunidades/{slug}', [InvestmentDiscoveryController::class, 'detail'])->name('portal.opportunity'); Route::get('/negocios', [PublicBusinessOpportunityController::class, 'index'])->name('business-marketplace.index'); diff --git a/tests/Feature/GlobalSearchTest.php b/tests/Feature/GlobalSearchTest.php new file mode 100644 index 00000000..10660758 --- /dev/null +++ b/tests/Feature/GlobalSearchTest.php @@ -0,0 +1,55 @@ +withoutVite(); + } + + public function test_empty_and_overlong_queries(): void + { + $this->get('/pesquisa')->assertOk()->assertInertia(fn (Assert $page) => $page->component('portal/search')->where('results.total', 0)); + $this->get('/pesquisa?q='.str_repeat('a', 121))->assertSessionHasErrors('q'); + } + + public function test_source_failure_recalculates_the_page_without_hiding_the_other_results(): void + { + $sources = [ + 'Documentos' => fn () => ['count' => 1, 'fetch' => fn () => throw new QueryException('testing', 'select 1', [], new \RuntimeException('Source unavailable'))], + 'Páginas' => fn () => ['count' => 1, 'fetch' => fn ($offset, $limit) => array_slice([['title' => 'Como investir', 'url' => '/como-investir']], $offset, $limit)], + ]; + $result = app(FederatedSearch::class)->page($sources, 1); + $this->assertSame(1, $result['total']); + $this->assertSame(['Documentos'], $result['unavailable']); + $this->assertSame('Como investir', $result['data'][0]['title']); + } + + public function test_public_search_uses_the_existing_portfolio_visibility_scope(): void + { + if (getenv('GLOBAL_SEARCH_QA') !== '1') { + $this->markTestSkipped('A prova com dados exige a base QA explicitamente seleccionada.'); + } + $identity = DB::selectOne('select current_database() as name, inet_server_port() as port'); + $this->assertSame('simulator_fiscal_delta_20261021_test', $identity->name); + $this->assertSame(55441, (int) $identity->port); + $record = InvestmentPortfolioController::visible()->first(); + if (! $record) { + $this->markTestSkipped('No published portfolio records in isolated QA.'); + } + $term = mb_substr($record->title, 0, 15); + $this->get('/pesquisa?q='.urlencode($term))->assertOk()->assertInertia(fn (Assert $page) => $page + ->component('portal/search')->where('query', $term) + ->where('results.total', fn ($total) => $total >= 1)); + } +} diff --git a/tests/Feature/TranslationApiTest.php b/tests/Feature/TranslationApiTest.php new file mode 100644 index 00000000..684a044d --- /dev/null +++ b/tests/Feature/TranslationApiTest.php @@ -0,0 +1,27 @@ + 'http://translator.test']); + Http::fake(['translator.test/languages' => Http::response([['code' => 'pt', 'name' => 'Portuguese', 'targets' => ['en']]])]); + + $this->getJson('/api/translate/languages')->assertOk()->assertJsonPath('0.targets.0', 'en'); + } + + public function test_it_translates_and_reports_engine_failures(): void + { + config(['services.translation.url' => 'http://translator.test']); + Http::fake(['translator.test/translate' => Http::sequence()->push(['translatedText' => 'Hello'])->push([], 500)]); + $input = ['text' => 'Olá', 'source_lang' => 'pt', 'target_lang' => 'en']; + + $this->postJson('/api/translate', $input)->assertOk()->assertJsonPath('translated_text', 'Hello'); + $this->postJson('/api/translate', $input)->assertStatus(503); + } +} diff --git a/tests/Unit/FederatedSearchTest.php b/tests/Unit/FederatedSearchTest.php new file mode 100644 index 00000000..244bd13e --- /dev/null +++ b/tests/Unit/FederatedSearchTest.php @@ -0,0 +1,47 @@ + $count, 'fetch' => function ($offset, $limit) use (&$calls, $prefix, $count) { + $calls[] = [$prefix, $offset, $limit]; + + return array_map(fn ($i) => ['title' => $prefix.$i, 'url' => '/'.$prefix.$i], array_slice(range(1, $count), $offset, $limit)); + }]; + }; + }; + $sources = ['Páginas' => $factory('p', 3), 'Oportunidades' => $factory('o', 7), 'Documentos' => $factory('d', 2)]; + $service = new FederatedSearch; + $first = $service->page($sources, 1); + $this->assertSame(['p1', 'p2', 'p3', 'o1', 'o2'], array_column($first['data'], 'title')); + $this->assertSame(['Páginas', 'Páginas', 'Páginas', 'Oportunidades', 'Oportunidades'], array_column($first['data'], 'category')); + $this->assertSame(12, $first['total']); + $this->assertSame(3, $first['last_page']); + $calls = []; + $second = $service->page($sources, 2); + $this->assertSame(['o3', 'o4', 'o5', 'o6', 'o7'], array_column($second['data'], 'title')); + $this->assertSame([['p', 0, 5], ['o', 0, 5], ['d', 0, 5], ['o', 5, 5]], $calls); + $third = $service->page($sources, 99); + $this->assertSame(3, $third['current_page']); + $this->assertSame(['d1', 'd2'], array_column($third['data'], 'title')); + } + + public function test_more_relevant_titles_appear_first_across_sources(): void + { + $factories = [ + 'Serviços' => fn () => ['count' => 1, 'fetch' => fn ($offset, $limit) => array_slice([['title' => 'Informação', 'url' => '/info', 'score' => 10]], $offset, $limit)], + 'Sectores' => fn () => ['count' => 1, 'fetch' => fn ($offset, $limit) => array_slice([['title' => 'Sectores', 'url' => '/sectores', 'score' => 100]], $offset, $limit)], + ]; + $result = (new FederatedSearch)->page($factories, 1, 5, fn ($item) => $item['score']); + $this->assertSame(['Sectores', 'Informação'], array_column($result['data'], 'title')); + } +} diff --git a/tests/Unit/SearchTermsTest.php b/tests/Unit/SearchTermsTest.php new file mode 100644 index 00000000..fe9a1f57 --- /dev/null +++ b/tests/Unit/SearchTermsTest.php @@ -0,0 +1,59 @@ +tokens(' NAMPULA agronegócio Agronegócio '); + + $this->assertSame(['nampula', 'agronegocio'], $tokens); + $this->assertTrue($search->matches('Agronegócio em Nampula', $tokens)); + $this->assertFalse($search->matches('Agronegócio em Gaza', $tokens)); + $this->assertSame([], $search->tokens(' ')); + } + + public function test_sql_matches_each_word_across_fields_with_bound_literal_patterns(): void + { + $connection = new PostgresConnection(fn () => throw new \LogicException('A database must not be opened.')); + $query = $connection->table('records'); + $search = new SearchTerms; + $search->constrain($query, ['title', 'description'], $search->tokens('100% a_b')); + + $sql = $query->toSql(); + $this->assertStringContainsString('translate(lower(coalesce(title::text', $sql); + $this->assertStringContainsString(' or ', $sql); + $this->assertStringContainsString(' and ', $sql); + $this->assertSame(['%100!%%', '%100!%%', '%a!_b%', '%a!_b%'], $query->getBindings()); + $this->assertStringNotContainsString('100%', $sql); + } + + public function test_static_services_can_be_found_without_a_database(): void + { + $pages = (new \ReflectionMethod(GlobalSearchController::class, 'pages'))->invoke(new GlobalSearchController); + $search = new SearchTerms; + $matches = array_values(array_filter($pages, fn ($page) => $search->matches(implode(' ', $page), $search->tokens('FISCAL simulador')))); + + $this->assertCount(1, $matches); + $this->assertSame('/simulador-fiscal', $matches[0]['url']); + } + + public function test_spelling_variants_common_words_and_title_relevance(): void + { + $search = new SearchTerms; + $tokens = $search->tokens('setores de investimento'); + $this->assertTrue($search->matches('Sectores de investimento', $tokens)); + $this->assertFalse($search->matches('Sector de saúde', $tokens)); + $this->assertTrue($search->matches('Projectos âncora', $search->tokens('projeto'))); + $this->assertTrue($search->matches('Oportunidades de investimento em energia', $search->tokens('Como investir em energia?'))); + $this->assertGreaterThan($search->score('Como investir', 'investir', ['investir']), $search->score('Investir', 'investir', ['investir'])); + $this->assertSame(['de'], $search->tokens('de')); + } +} diff --git a/tests/frontend/fiscal-form-entry.test.mjs b/tests/frontend/fiscal-form-entry.test.mjs index 201561b8..8b074077 100644 --- a/tests/frontend/fiscal-form-entry.test.mjs +++ b/tests/frontend/fiscal-form-entry.test.mjs @@ -7,53 +7,14 @@ import React from 'react'; import { renderToStaticMarkup } from 'react-dom/server'; import ts from 'typescript'; import * as forms from '../../resources/js/lib/fiscal-form-preparation.ts'; -const require = createRequire(import.meta.url); -const source = fs.readFileSync( - new URL( - '../../resources/js/pages/portal/fiscal-forms.tsx', - import.meta.url, - ), - 'utf8', -); -const compiled = ts.transpileModule(source, { - compilerOptions: { - module: ts.ModuleKind.CommonJS, - target: ts.ScriptTarget.ES2022, - jsx: ts.JsxEmit.ReactJSX, - }, -}).outputText; -const module = { exports: {} }; -compileFunction(compiled, ['require', 'module', 'exports'])( - (name) => - name === '@inertiajs/react' - ? { - Head: () => null, - Link: ({ href, children, ...props }) => - React.createElement('a', { href, ...props }, children), - router: { on: () => () => {} }, - } - : name === '@/layouts/portal-navigation' - ? { PortalMenu: () => null } - : name === '@/lib/fiscal-form-preparation' - ? forms - : require(name), - module, - module.exports, -); -void test('fiscal entry renders labelled editable fields and a guided preparation rather than legislation', () => { - const html = renderToStaticMarkup( - React.createElement(module.exports.default), - ); - for (const label of [ - 'Identificação e período', - 'Nome ou designação social', - 'NUIT', - 'Período — ano e mês', - 'Conferir e guardar', - 'Guardar ficheiro para continuar', - 'Recuperar ficheiro guardado', - 'Confirmar recuperação', - ]) { +const require=createRequire(import.meta.url); +const source=fs.readFileSync(new URL('../../resources/js/pages/portal/fiscal-forms.tsx',import.meta.url),'utf8'); +const compiled=ts.transpileModule(source,{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2022,jsx:ts.JsxEmit.ReactJSX}}).outputText; +const module={exports:{}}; +new Function('require','module','exports',compiled)(name=>name==='@inertiajs/react'?{Head:()=>null,Link:({href,children,...props})=>React.createElement('a',{href,...props},children),router:{on:()=>()=>{}}}:name==='@/layouts/portal-navigation'?{PortalMenu:()=>null}:name==='@/lib/fiscal-form-preparation'?forms:require(name),module,module.exports); +test('fiscal entry renders labelled editable fields and a guided preparation rather than legislation',()=>{ + const html=renderToStaticMarkup(React.createElement(module.exports.default)); + for(const label of ['Identificação e período','Nome ou designação social','NUIT','Período — ano e mês','Conferir e guardar','Guardar ficheiro para continuar','Recuperar ficheiro guardado','Confirmar recuperação']) { // Confirmation appears only after a compatible file is selected. if (label !== 'Confirmar recuperação') assert.ok(html.includes(label), label); diff --git a/tests/frontend/global-search.test.mjs b/tests/frontend/global-search.test.mjs new file mode 100644 index 00000000..1820cd39 --- /dev/null +++ b/tests/frontend/global-search.test.mjs @@ -0,0 +1,36 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import { createRequire } from 'node:module'; +import React from 'react'; +import { renderToStaticMarkup } from 'react-dom/server'; +import ts from 'typescript'; + +const require = createRequire(import.meta.url); +const source = fs.readFileSync(new URL('../../resources/js/pages/portal/search.tsx', import.meta.url), 'utf8'); +const compiled = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022, jsx: ts.JsxEmit.ReactJSX } }).outputText; +const module = { exports: {} }; +new Function('require', 'module', 'exports', compiled)(name => name === '@inertiajs/react' ? { + Head: () => null, + Link: ({ href, children, ...props }) => React.createElement('a', { href, ...props }, children), +} : name === 'lucide-react' ? { Search: () => null } : require(name), module, module.exports); +const Search = module.exports.default; + +test('shows five results in one sequence with numbered pagination and the query preserved', () => { + const results = { data: [{ title: 'Energia em Nampula', description: 'Projecto público', url: '/oportunidades/energia', category: 'Oportunidades' }], total: 12, current_page: 1, last_page: 3, per_page: 5, unavailable: [] }; + const html = renderToStaticMarkup(React.createElement(Search, { query: 'energia', results })); + assert.match(html, /Oportunidades/); + assert.match(html, /Resultados 1–1 de 12/); + assert.match(html, /href="\/oportunidades\/energia"/); + assert.match(html, /q=energia&page=2/); + assert.match(html, /aria-current="page"/); + assert.doesNotMatch(html, /emerald/); +}); + +test('makes source failures explicit without hiding other results', () => { + const results = { data: [], total: 0, current_page: 1, last_page: 1, per_page: 5, unavailable: ['Documentos e legislação'] }; + const html = renderToStaticMarkup(React.createElement(Search, { query: 'terra', results })); + assert.match(html, /role="alert"/); + assert.match(html, /Não foi possível consultar: Documentos e legislação/); + assert.match(html, /Nenhum resultado nas fontes consultadas/); +}); diff --git a/tests/frontend/quick-opportunity-search.test.mjs b/tests/frontend/quick-opportunity-search.test.mjs index 96dc30bf..31283ba7 100644 --- a/tests/frontend/quick-opportunity-search.test.mjs +++ b/tests/frontend/quick-opportunity-search.test.mjs @@ -55,7 +55,7 @@ await test('the quick search has a native GET destination and submits the actual const input = nodes(tree).find( (node) => node.type === 'input' && node.props.name === 'q', ); - assert.equal(form.props.action, '/oportunidades'); + assert.equal(form.props.action, '/pesquisa'); assert.equal(form.props.method, 'get'); assert.equal(input.props.maxLength, 120); input.props.onChange({ target: { value: 'plantio de algodao' } }); @@ -80,15 +80,15 @@ await test('the quick search has a native GET destination and submits the actual }); assert.equal(prevented, true); assert.deepEqual(sent, { - url: '/oportunidades', + url: '/pesquisa', data: { q: 'plantio de algodao' }, }); } finally { globalThis.FormData = original; } const html = renderToStaticMarkup(tree); - assert.ok(html.includes('Pesquisa rápida de oportunidades')); - assert.ok(html.includes('action="/oportunidades"')); + assert.ok(html.includes('Pesquisa rápida no InvesteMoz')); + assert.ok(html.includes('action="/pesquisa"')); assert.ok(html.includes('Pesquisar')); });