diff --git a/app/Http/Controllers/OpportunityOriginReviewController.php b/app/Http/Controllers/OpportunityOriginReviewController.php new file mode 100644 index 00000000..df8a0075 --- /dev/null +++ b/app/Http/Controllers/OpportunityOriginReviewController.php @@ -0,0 +1,32 @@ + Opportunity::whereIn('reference', ['INV-MZ-AGR-001', 'INV-MZ-ENE-002', 'INV-MZ-TUR-003', 'INV-MZ-INF-004', 'INV-MZ-BLUE-005', 'INV-MZ-FIN-006'])->orderBy('id')->pluck('id')->map(fn ($id) => $review->state($id))->all(), 'ready' => $review->ready()]); + } + + public function preview(Request $request, OpportunityOriginReview $review): JsonResponse + { + $data = $request->validate(['id' => ['required', 'integer', 'exists:opportunities,id'], 'classification' => ['required', 'in:real,example,undetermined'], 'reason' => ['required', 'string', 'max:2000']]); + + return response()->json($review->preview($data['id'], $data['classification'], $data['reason'])); + } + + public function apply(Request $request, OpportunityOriginReview $review): JsonResponse + { + $data = $request->validate(['id' => ['required', 'integer', 'exists:opportunities,id'], 'classification' => ['required', 'in:real,example,undetermined'], 'reason' => ['required', 'string', 'max:2000'], 'snapshot_hash' => ['required', 'string', 'size:64'], 'confirm' => ['required', 'accepted']]); + + return response()->json($review->apply($data['id'], $data['classification'], $data['reason'], $data['snapshot_hash'], $request->user()->id)); + } +} diff --git a/app/Models/Opportunity.php b/app/Models/Opportunity.php index 2ce2f6ad..d7552634 100644 --- a/app/Models/Opportunity.php +++ b/app/Models/Opportunity.php @@ -58,6 +58,7 @@ class Opportunity extends Model */ public function scopePublished(Builder $query): Builder { - return $query->where('status', 'published')->whereNotNull('published_at')->where('published_at', '<=', now()); + return $query->where('status', 'published')->whereNotNull('published_at')->where('published_at', '<=', now()) + ->whereRaw("COALESCE((SELECT origin.after_data->>'classification' FROM audit_events origin WHERE origin.source_system='opportunity-origin-review' AND origin.after_data->>'opportunity_id'=opportunities.id::text ORDER BY origin.occurred_at DESC,origin.id DESC LIMIT 1),'undetermined') <> 'example'"); } } diff --git a/app/Services/OpportunityOriginReview.php b/app/Services/OpportunityOriginReview.php new file mode 100644 index 00000000..73f4cdf6 --- /dev/null +++ b/app/Services/OpportunityOriginReview.php @@ -0,0 +1,77 @@ + hash_file('sha256', base_path($file)), ['app/Services/OpportunityOriginReview.php', 'app/Models/Opportunity.php', 'app/Http/Controllers/OpportunityOriginReviewController.php', 'resources/js/pages/management/opportunity-origin.tsx']))); + } + + /** @return array */ + public function state(int $id): array + { + $record = Opportunity::findOrFail($id); + $events = DB::table('audit_events')->where('source_system', 'opportunity-origin-review')->where('after_data->opportunity_id', $id)->orderByDesc('occurred_at')->orderByDesc('id')->get(); + $history = $events->map(function ($event) { + $data = json_decode($event->after_data, true); + + return ['id' => $event->id, 'classification' => $data['classification'], 'reason' => $data['reason'], 'author' => $event->actor_user_id, 'date' => $event->occurred_at]; + })->all(); + + return ['id' => $id, 'reference' => $record->reference, 'title' => $record->title, 'description' => $record->description, 'classification' => $history[0]['classification'] ?? 'undetermined', 'history' => $history, + 'snapshot_hash' => hash('sha256', json_encode([$record->getRawOriginal(), $history[0]['id'] ?? null], JSON_THROW_ON_ERROR))]; + } + + /** @return array */ + public function preview(int $id, string $classification, string $reason): array + { + abort_unless(in_array($classification, ['real', 'example', 'undetermined'], true) && trim($reason) !== '', 422, 'Classificação e fundamento são necessários.'); + + return ['current' => $this->state($id), 'classification' => $classification, 'reason' => trim($reason), 'effect' => $classification === 'example' ? 'Separar do catálogo público, API, pesquisa e mapa; consulta autenticada de demonstração preservada.' : 'Elegível para os filtros públicos editoriais existentes; indeterminado não comprova realidade.', 'relationships_changed' => false, 'ready' => $this->ready()]; + } + + public function ready(): bool + { + $path = config('opportunity_origin.proof_path', storage_path('app/private/investmoz-v2/opportunity-origin-proof.json')); + if (! is_file($path)) { + return false; + } + $proof = json_decode(file_get_contents($path) ?: '{}', true); + $identity = DB::selectOne('SELECT current_database() AS database,inet_server_port() AS port'); + + return ($proof['restore_verified'] ?? false) && ($proof['reversal_verified'] ?? false) && ($proof['code_hash'] ?? '') === $this->fingerprint() + && ($proof['target']['database'] ?? '') === $identity->database && (int) ($proof['target']['port'] ?? 0) === (int) $identity->port + && strtotime($proof['verified_at'] ?? '') >= time() - 86400 && is_file($proof['dump_path'] ?? '') && hash_file('sha256', $proof['dump_path']) === ($proof['sha256'] ?? null); + } + + /** @return array */ + public function apply(int $id, string $classification, string $reason, string $snapshotHash, int $actor): array + { + $preview = $this->preview($id, $classification, $reason); + abort_unless($preview['ready'], 422, 'Prova atual de backup/restauro e reversão necessária.'); + if (app()->environment('testing')) { + $identity = DB::selectOne('SELECT current_database() AS database,inet_server_port() AS port'); + abort_unless(str_ends_with($identity->database, '_test') && (int) $identity->port === 55441, 409, 'QA exige base isolada.'); + } + + return DB::transaction(function () use ($id, $classification, $reason, $snapshotHash, $actor) { + DB::select('SELECT pg_advisory_xact_lock(91026, ?)', [$id]); + DB::table('opportunities')->where('id', $id)->lockForUpdate()->first(); + $current = $this->state($id); + if ($current['classification'] === $classification && ($current['history'][0]['reason'] ?? null) === trim($reason)) { + return ['replayed' => true, 'changed' => 0]; + } + abort_unless(hash_equals($current['snapshot_hash'], $snapshotHash), 409, 'Registo/decisão alterado; reveja a pré-visualização.'); + DB::table('audit_events')->insert(['source_system' => 'opportunity-origin-review', 'entity_type' => 'opportunity_origin', 'action' => 'origin_classification_decided', 'actor_user_id' => $actor, 'data_classification' => 'internal', 'occurred_at' => DB::raw('clock_timestamp()'), + 'before_data' => json_encode(['classification' => $current['classification']], JSON_THROW_ON_ERROR), + 'after_data' => json_encode(['opportunity_id' => $id, 'classification' => $classification, 'reason' => trim($reason), 'reference' => $current['reference'], 'relationships_changed' => false], JSON_THROW_ON_ERROR)]); + + return ['replayed' => false, 'changed' => 1]; + }); + } +} diff --git a/docs/investmoz/v2/ativacao-rastreabilidade-territorial.md b/docs/investmoz/v2/ativacao-rastreabilidade-territorial.md index 6ead1dcd..4931bee5 100644 --- a/docs/investmoz/v2/ativacao-rastreabilidade-territorial.md +++ b/docs/investmoz/v2/ativacao-rastreabilidade-territorial.md @@ -66,3 +66,11 @@ Foi corrigida uma fragilidade de interação: o mesmo botão mudava de aplicar p Os três testes afetados passaram, com 30 asserções, incluindo recusa de reversão sem motivo/confirmação e conservação do motivo no histórico. TypeScript, formatação/lint, PHPStan e build passaram. As pendências de rastreabilidade, jurídicas específicas, cobertura documental, ClamD e primeiro pedido real permanecem separadas. **Não houve reaplicação automática na base ativa.** O navegador QA confirmou pré-visualização, aplicação, recuperação e reversão após preencher motivo e confirmação; o botão de reversão estava inicialmente desativado. Sem erros de JavaScript. Leitura ativa final confirmou todas as guardas atuais do novo plano e apenas os dois eventos históricos, sendo o último a reversão. + +## Confirmação da aplicação do novo plano + +Leitura `READ ONLY` em 09/10/2026 às 02:18:17 locais: o novo plano foi aplicado às **02:16:24**, pela conta **13**. Evento `traceability_applied`, ID `62ca4e39-93b3-4de1-b575-b15bc026d2e4`, corresponde ao hash `ec11b9e400b7b127fbc8d42a2afe5e4d2c8a3919216a5d7439fb38b0751c57c6`, com 112 entradas e 13 alterações. Não há reversão deste novo plano nesta leitura. + +As 13 referências persistidas correspondem exatamente à proposta e ao evento de auditoria; versões passaram de 3 para 4. Validações e tipos das associações foram preservados. A comparação dos estados existentes nas guardas confirma preservação, incluindo as 14 zonas `validated` e os seis estados legais `pending` preexistentes. **Estado atual: novo incremento aplicado na base ativa pelo fluxo autenticado.** Histórico da aplicação/reversão anterior permanece intacto. + +Evidência privada: `territory-new-plan-confirmation.json`, com verificação por associação. Esta confirmação não escreveu na base ativa nem repetiu a aplicação. Pendências documentais específicas, cobertura do inventário, ClamD e primeiro pedido real mantêm os seus estados independentes. diff --git a/docs/investmoz/v2/backlog-mestre.md b/docs/investmoz/v2/backlog-mestre.md index 14a16ccd..75754d3a 100644 --- a/docs/investmoz/v2/backlog-mestre.md +++ b/docs/investmoz/v2/backlog-mestre.md @@ -171,3 +171,29 @@ Leitura `READ ONLY` em 09/10/2026 às 02:10:09 locais: auditoria confirma aplica ### Novo plano após reversão — 9 de outubro O motivo humano não consta do evento original e não foi inventado. Corrigida a possibilidade de reversão imediata pelo botão que mudava após aplicação: exige agora motivo e confirmação expressa, conservados na auditoria; planos revertidos ficam desativados. Novo plano preparado com as versões 3 atuais das 13 associações, mantendo 112 entradas e validações existentes. Backup/restauro atualizado: 288 tabelas e cinco ficheiros em `desk_restore_20261009_001345_test:55441`; aplicação/idempotência/reversão verificadas na cópia. Três testes afetados/30 asserções, TypeScript, lint, PHPStan e build passaram. [Pré-visualização e prova nova](ativacao-rastreabilidade-territorial.md): válida até 10/10 às 02:13:54 locais. **Não reaplicado na base ativa; ação autenticada permanece por executar.** + +### Aplicação do novo plano confirmada + +Leitura `READ ONLY` às 02:18:17 locais de 09/10/2026: **V2-08/V2-13 aplicado na base ativa**, pelo utilizador 13 às 02:16:24, evento `62ca4e39-93b3-4de1-b575-b15bc026d2e4`. As 13 alterações correspondem à proposta e auditoria, com versões 4; 112 entradas registadas. Validações e tipos de associação, 14 zonas validadas e seis estados legais pendentes preexistentes preservados. Sem reversão do novo plano nesta leitura. Evidência privada: `territory-new-plan-confirmation.json`. Nenhuma escrita durante a confirmação; restantes pendências independentes não foram concluídas por esta aplicação. + +### Associações solicitadas pelo proprietário — estado global + +Comparação `READ ONLY` de todas as linhas do plano original: **28/28 associações projeto–território e 13/13 associações cluster–projeto presentes na base ativa**, sem pares ausentes. Evidência por associação: `owner-associations-confirmation.json`. As referências V2 complementam relações já aplicadas; não há necessidade de nova fonte nem de duplicar estas associações. A comunicação anterior sobre «nenhuma nova localização nesta aplicação» fica esclarecida pelo estado global acima. Correspondências das seis oportunidades genéricas e conflitos de identidade dos componentes preparados continuam questões concretas de ligação entre registos, não de credibilidade das fontes oficiais. + +### Correspondências das seis oportunidades — revisão individual + +[Ficha por ID, título, descrição, opções e trechos](oportunidades-correspondencias.md). Foram pesquisadas as extrações disponíveis de 35 documentos e 13 433 parágrafos do mestre relido, incluindo exemplos de cadastro/código. Não foi recuperada identificação inequívoca que ligue os seis registos existentes às âncoras/clusters; citações de âmbito/atividade são apresentadas como opções, sem inferência de identidade. Isto não reabre a aceitação oficial e não declara revisão integral da cobertura documental. + +Plano único privado `opportunity-association-plan.json`: zero inserções e zero eliminações enquanto os pares permanecerem indeterminados. QA `desk_restore_20261009_001345_test:55441` confirmou os seis registos e relações existentes, sem aplicar opções nem criar duplicados; evidência `opportunity-association-qa.json`. Os pares decididos integrarão o mesmo plano, com preparação QA, deduplicação, backup/restauro, reversão e aplicação autenticada. As 28 localizações e 13 relações já aplicadas são preservadas; nenhuma escrita ativa nesta revisão. + +### Origem e realidade das seis oportunidades + +[Evidência, perguntas ao responsável e plano de separação](origem-oportunidades-e-separacao.md). Os seis registos coincidem nos oito campos comparados com `OpportunitySeeder.php`; o seeder define publicação, mas não comprova realidade do investimento. A cadeia atual de `DatabaseSeeder` não chama esse seeder. Auditoria disponível: nove edições posteriores, sem prova de execução/criação histórica nem designação do responsável. Real/exemplo/indeterminado e pertença/serviço permanecem decisões individuais, não inferências pelo setor ou pela origem de código. + +Plano condicional de exemplos: classificação explícita pela auditoria existente, rótulo e consulta autenticada separada do catálogo/API/mapa públicos, preservando IDs e relações/interações/histórico. **Implementado e validado em QA; nenhuma decisão aplicada na base ativa**. Nenhum contacto externo enviado. As fontes oficiais já aceites não foram revalidadas. Evidência privada: `opportunity-origin-confirmation.json`. + +### V2-06/V2-08/V2-13 — revisão autenticada da origem + +[Relatório e procedimento](revisao-origem-oportunidades.md). Página `/management/opportunity-origin`: seis itens, decisões real/exemplo/indeterminado, fundamento obrigatório, autor/data e pré-visualização antes de confirmação individual. Nenhuma classificação inferida por ausência de prova ou coincidência com seeder; associações a projetos/clusters permanecem separadas. + +**QA:** 58 testes/917 asserções passaram; percurso no navegador com separação/reversão, API/mapa e consulta autorizada. Backup/restauro de 288 tabelas/cinco ficheiros; comparação de dez tabelas após ensaio demonstrou preservação. **Ativo:** leitura com zero decisões de classificação; sem escritas nesta entrega. Prova preparada válida até 10/10/2026 02:38:49 locais, aplicação exclusivamente autenticada após pré-visualização. Cobertura documental, questões jurídicas, ClamD e primeiro pedido real mantêm estados próprios. diff --git a/docs/investmoz/v2/oportunidades-correspondencias.md b/docs/investmoz/v2/oportunidades-correspondencias.md new file mode 100644 index 00000000..4ecaaf82 --- /dev/null +++ b/docs/investmoz/v2/oportunidades-correspondencias.md @@ -0,0 +1,116 @@ +# Oportunidades — correspondências e plano único + +Leitura protegida: 2026-10-09T00:32:17+00:00, investemoz:5432. Consulta das extrações de 35 documentos e 13433 parágrafos do mestre relido; não é revisão integral dos ficheiros do inventário. A aceitação das fontes oficiais não foi reaberta. + +**Resultado:** zero ligações inequívocas recuperadas para as seis oportunidades existentes. As opções abaixo são alternativas para decisão, não correspondências confirmadas. O trecho documenta o projeto/cluster candidato; não identifica automaticamente a oportunidade como seu componente. + +As referências de programação e os exemplos de código descrevem registos e funções. Não são reinterpretados como identidade institucional, promotor ou relação oficial. As correspondências continuam pendências específicas, independentes da cobertura do inventário, questões jurídicas, ClamD e primeiro pedido real. + +## Origem e perguntas ao responsável + +Ver [a conferência do seeder, a auditoria disponível e o plano condicional de separação](origem-oportunidades-e-separacao.md). Os registos coincidem com dados de código; isto não comprova investimentos reais nem autoriza classificá-los automaticamente como exemplos. Antes de decidir os pares, esclarecer projeto/polo dos IDs 1–3, localização/corredor do ID 4, integração em A12 ou independência do ID 5 e pertença versus serviço financeiro do ID 6. Nenhum contacto foi enviado e nenhuma decisão foi inventada. + +## ID 1 — Unidade de processamento agrícola em Nampula + +Referência: `INV-MZ-AGR-001`. Descrição atual: 120 empregos directos e substituição de importações. + +Relações já cadastradas: 0 projetos, 0 clusters. + +### Opções documentadas, sem associação automática + +- **A03 — Programa de Desenvolvimento Integrado do Corredor de Nacala**, ID `10a6e456-b287-4cf7-9ce6-ccd4e28a0684`. Fonte: upgrade do portal de investimentos v4 programacao.docx — 6.1 / 6.6, dossier `39f5973c-f4cd-49d0-aeea-1c0c71783550`, campo `description`. Trecho: «Programa integrado que combina porto, ferrovia, indústria, agricultura, energia, água e serviços para transformar o Norte num corredor produtivo e exportador.». **Não prova a relação com a oportunidade 1.** +- **CLU-01 — Agro-indústria e segurança alimentar**, ID `2b37d84a-0b71-4b87-b577-6f550954c908`. Fonte: upgrade do portal de investimentos v4 programacao.docx — 4.5.2, dossier `ca6a1427-0e2b-406e-94bd-da23630d5ec4`, campo `anchors`. Trecho: «A06, A14 e A15; arroz, regadio, processamento, armazenamento, logística e comercialização.». **Não prova a relação com a oportunidade 1.** + +**Dúvida concreta:** Qual é a atividade/cultura e o código do componente desta unidade? Integra expressamente o Corredor de Nacala ou apenas o âmbito de processamento do CLU-01? A06 refere a cadeia do arroz no Centro, não identifica esta unidade. + +**Identificação no acervo revisto:** Reformulacao do portal do investidor 1.docx, parágrafo extraído 320: «INV-MZ-AGR-001]»; Reformulacao do portal do investidor 1.docx, parágrafo extraído 869: «INV-MZ-AGR-001", "title": "Unidade de processamento agrícola", "sector": "Agronegócio", "province": "Nampula", "maturity": "Projecto estruturado", "estimatedInvestment": { "amount": 2500000, "currency": "USD" }, "actions": ["Ver ficha", "Ma»; Reformulacao do portal do investidor 1.docx, parágrafo extraído 993: «Unidade de processamento agrícola em Nampula', 'province' => 'Nampula', 'district' => 'Nampula', 'amount' => 2500000, 'modality' => 'Capital ou joint venture', 'maturity' => 4], ['sector' => 'Energia e Mineração', 'title' => 'Par». Ocorrência de exemplo/código não constitui ligação a uma âncora. + +## ID 2 — Mini-rede solar para polo produtivo + +Referência: `INV-MZ-ENE-002`. Descrição atual: Acesso a energia para 40 unidades produtivas. + +Relações já cadastradas: 0 projetos, 0 clusters. + +### Opções documentadas, sem associação automática + +- **A08 — Zona Económica Especial de Mocuba**, ID `1169d03c-fe38-47c5-a54c-286a3f126ce4`. Fonte: upgrade do portal de investimentos v4 programacao.docx — 6.1 / 6.6, dossier `81d775e5-5dff-4fa2-98b1-72affb0a1082`, campo `description`. Trecho: «Ecossistema industrial e agro-logístico para transformar a Zambézia num pólo de processamento, inovação, energia e exportação.». **Não prova a relação com a oportunidade 2.** +- **CLU-02 — Energia e industrialização**, ID `e19235e0-0a36-4e65-8a61-07f1d52f07d1`. Fonte: upgrade do portal de investimentos v4 programacao.docx — 4.5.2, dossier `3cd11616-9099-477c-bf3e-09cddca74053`, campo `gaps`. Trecho: «Subestações, geração complementar, manutenção, equipamentos, utilities, competências e contratos de compra.». **Não prova a relação com a oportunidade 2.** + +**Dúvida concreta:** Qual é o polo produtivo e o código da mini-rede que abastece as 40 unidades? A fonte não identifica se faz parte de Mocuba, de outro parque ou de um projeto independente; geração solar não é identidade com A10 hidroelétrica. + +**Identificação no acervo revisto:** Reformulacao do portal do investidor 1.docx, parágrafo extraído 3091: «INV-MZ-ENE-002',»; Reformulacao do portal do investidor 1.docx, parágrafo extraído 3092: «Mini-rede solar para polo produtivo',»; Reformulacao do portal do investidor 1.docx, parágrafo extraído 3101: «inv-mz-ene-002',». Ocorrência de exemplo/código não constitui ligação a uma âncora. + +## ID 3 — Eco-lodge costeiro com integração comunitária + +Referência: `INV-MZ-TUR-003`. Descrição atual: Emprego local e conservação da faixa costeira. + +Relações já cadastradas: 0 projetos, 0 clusters. + +### Opções documentadas, sem associação automática + +- **A13 — Vilanculos – Região Integrada de Turismo**, ID `819b8981-55e3-471a-b95d-7e9047c3dcdc`. Fonte: upgrade do portal de investimentos v4 programacao.docx — 6.1 / 6.6, dossier `bb15487f-1617-49e6-86b7-d617812b658b`, campo `description`. Trecho: «Destino turístico costeiro integrado que combina hotelaria, acessibilidade, energia, água, formação, economia azul e serviços digitais.». **Não prova a relação com a oportunidade 3.** +- **CLU-04 — Turismo costeiro e economia azul**, ID `379c7d07-c767-4ab5-80db-3211f29eaf9e`. Fonte: upgrade do portal de investimentos v4 programacao.docx — 4.5.2, dossier `5f0ff662-dc74-41de-adcd-de784a595321`, campo `gaps`. Trecho: «Alojamento, formação, mobilidade, água, energia, serviços digitais, conservação e experiências locais.». **Não prova a relação com a oportunidade 3.** + +**Dúvida concreta:** Qual é o nome/local exato do empreendimento e o componente documental? Está integrado em Vilanculos–Inhassoro–Bazaruto ou é um empreendimento costeiro independente? Inhambane e eco-lodge não demonstram essa integração. + +**Identificação no acervo revisto:** Reformulacao do portal do investidor 1.docx, parágrafo extraído 4334: «INV-MZ-TUR-003',»; Reformulacao do portal do investidor 1.docx, parágrafo extraído 4335: «Eco-lodge costeiro com integração comunitária',»; Reformulacao do portal do investidor 1.docx, parágrafo extraído 9284: «INV-MZ-TUR-003',». Ocorrência de exemplo/código não constitui ligação a uma âncora. + +## ID 4 — Centro logístico provincial + +Referência: `INV-MZ-INF-004`. Descrição atual: Redução de custos logísticos regionais. + +Relações já cadastradas: 0 projetos, 0 clusters. + +### Opções documentadas, sem associação automática + +- **A02 — Linha Ferroviária Norte–Sul**, ID `573bf0d6-8ed0-4167-9942-50e0775bf109`. Fonte: upgrade do portal de investimentos v4 programacao.docx — 6.1 / 6.6, dossier `1125f069-61e2-4071-99f3-dd103caf3156`, campo `description`. Trecho: «Ligação ferroviária contínua entre Norte, Centro e Sul para reduzir custos logísticos, integrar mercados e reforçar Moçambique como plataforma regional.». **Não prova a relação com a oportunidade 4.** +- **A03 — Programa de Desenvolvimento Integrado do Corredor de Nacala**, ID `10a6e456-b287-4cf7-9ce6-ccd4e28a0684`. Fonte: upgrade do portal de investimentos v4 programacao.docx — 6.1 / 6.6, dossier `39f5973c-f4cd-49d0-aeea-1c0c71783550`, campo `description`. Trecho: «Programa integrado que combina porto, ferrovia, indústria, agricultura, energia, água e serviços para transformar o Norte num corredor produtivo e exportador.». **Não prova a relação com a oportunidade 4.** +- **A11 — Corredor Ferroviário e Portuário de Macuse–Moatize**, ID `9c2398a0-e3ae-4225-b198-2eec9674a37f`. Fonte: upgrade do portal de investimentos v4 programacao.docx — 6.1 / 6.6, dossier `72c13e83-e8dd-465d-99a9-eb0ccaa4fd0b`, campo `description`. Trecho: «Corredor ferroviário e porto de águas profundas para ligar Tete à costa, diversificar rotas e aumentar a capacidade exportadora.». **Não prova a relação com a oportunidade 4.** +- **CLU-03 — Logística, corredores e exportação**, ID `6c7da2d1-acd0-42aa-af39-9397fae9f557`. Fonte: upgrade do portal de investimentos v4 programacao.docx — 4.5.2, dossier `ec9494a2-22cc-423e-8a9e-efa1e05ac2b4`, campo `gaps`. Trecho: «Última milha, intermodalidade, terminais, plataformas logísticas, digitalização, fronteiras e carga contratual.». **Não prova a relação com a oportunidade 4.** + +**Dúvida concreta:** Qual é o terminal/plataforma, o corredor servido e o código contratual ou documental? A redução de custos logísticos não determina se integra Norte–Sul, Nacala, Macuse–Moatize ou nenhum deles; Sofala não decide a ligação. + +**Identificação no acervo revisto:** Reformulacao do portal do investidor 1.docx, parágrafo extraído 993: «Centro logístico provincial', 'province' => 'Sofala', 'district' => 'Beira', 'amount' => 8500000, 'modality' => 'Concessão ou PPP', 'maturity' => 4], ]; foreach ($items as $item) { $sector = Sector::where('name', $i»; Reformulacao do portal do investidor 1.docx, parágrafo extraído 9306: «INV-MZ-INF-004',»; Reformulacao do portal do investidor 1.docx, parágrafo extraído 9307: «Centro logístico provincial',». Ocorrência de exemplo/código não constitui ligação a uma âncora. + +## ID 5 — Processamento de pescado e cadeia fria + +Referência: `INV-MZ-BLUE-005`. Descrição atual: Redução de perdas pós-captura na pesca artesanal. + +Relações já cadastradas: 0 projetos, 0 clusters. + +### Opções documentadas, sem associação automática + +- **A12 — Programa de Conectividade e Resiliência Costeira (Compacto II)**, ID `59f9d973-4974-4d54-8f25-2af5188801df`. Fonte: upgrade do portal de investimentos v4 programacao.docx — 6.1 / 6.6, dossier `a023d6b2-1fa2-4fe7-9709-f93ae1f10e99`, campo `description`. Trecho: «Programa integrado para melhorar conectividade rural, investimento agrícola e resiliência de comunidades e ecossistemas costeiros da Zambézia.». **Não prova a relação com a oportunidade 5.** +- **CLU-04 — Turismo costeiro e economia azul**, ID `379c7d07-c767-4ab5-80db-3211f29eaf9e`. Fonte: upgrade do portal de investimentos v4 programacao.docx — 4.5.2, dossier `5f0ff662-dc74-41de-adcd-de784a595321`, campo `anchors`. Trecho: «A04 e A13; Crusse Jamali, Vilanculos, Bazaruto, hotelaria, marinas e aviação.». **Não prova a relação com a oportunidade 5.** + +**Dúvida concreta:** Qual é a unidade de pescado e o componente do programa ao qual pertence? A12 identifica resiliência costeira, mas não esta unidade de processamento. CLU-04 identifica turismo/marinas, não demonstra cadeia fria pesqueira; pode não haver âncora/cluster correspondente no cadastro atual. + +**Identificação no acervo revisto:** Reformulacao do portal do investidor 1.docx, parágrafo extraído 9325: «INV-MZ-BLUE-005',»; Reformulacao do portal do investidor 1.docx, parágrafo extraído 9326: «Processamento de pescado e cadeia fria',»; Reformulacao do portal do investidor v2.docx, parágrafo extraído 9475: «INV-MZ-BLUE-005',». Ocorrência de exemplo/código não constitui ligação a uma âncora. + +## ID 6 — Plataforma de crédito para PME agrícolas + +Referência: `INV-MZ-FIN-006`. Descrição atual: Acesso a financiamento para 300+ pequenas empresas. + +Relações já cadastradas: 0 projetos, 0 clusters. + +### Opções documentadas, sem associação automática + +- **CLU-08 — Financiamento do investimento**, ID `37fd560d-74ad-416d-8ad4-15db3a4739ba`. Fonte: upgrade do portal de investimentos v4 programacao.docx — 4.5.2, dossier `ed2ea4e4-fca7-4cc8-bb8c-f99a1dfda73e`, campo `anchors`. Trecho: «Bancos, DFI, fundos climáticos, garantias, venture capital, crédito sectorial e assistência técnica.». **Não prova a relação com a oportunidade 6.** +- **CLU-07 — Tecnologia e serviços digitais**, ID `7f224d3a-3c82-42b1-bd18-ad9ab8afac1e`. Fonte: upgrade do portal de investimentos v4 programacao.docx — 4.5.2, dossier `523d2aa1-4c7d-4a84-97fc-220f4e845fd7`, campo `anchors`. Trecho: «Fintech, govtech, centros de dados, incubação, formação e serviços empresariais.». **Não prova a relação com a oportunidade 6.** +- **CLU-01 — Agro-indústria e segurança alimentar**, ID `2b37d84a-0b71-4b87-b577-6f550954c908`. Fonte: upgrade do portal de investimentos v4 programacao.docx — 4.5.2, dossier `ca6a1427-0e2b-406e-94bd-da23630d5ec4`, campo `gaps`. Trecho: «Irrigação, sementes, mecanização, frio, silos, embalagem, certificação, financiamento e compradores.». **Não prova a relação com a oportunidade 6.** + +**Dúvida concreta:** Qual é o operador/instrumento e a relação assumida: financiamento transversal CLU-08, serviço fintech CLU-07 ou financiamento da cadeia CLU-01? Qual documento identifica esta plataforma e as 300+ empresas? Não atribuir todos os clusters automaticamente. + +**Identificação no acervo revisto:** Não foi encontrada menção exata da referência/título/descrição nas extrações revistas. Não implica invalidade da fonte ou do registo. + +## Plano único de novas ligações + +1. Preservar todas as relações existentes, incluindo as 28 associações territoriais e 13 relações cluster–projeto já conferidas. +2. Decidir os pares dos seis registos acima e o âmbito da relação, podendo optar por nenhuma correspondência. Não exigir outra fonte para revalidar origem oficial aceite. +3. Adicionar ao mesmo plano apenas pares identificados explicitamente, com trecho ou decisão de correspondência do proprietário; verificar os IDs atuais e excluir os pares já cadastrados. +4. Preparar os pares decididos na base QA isolada, testar idempotência e os filtros/fichas/mapa, sem converter contexto em implantação. +5. Atualizar backup/restauro, verificar reversão das inserções específicas e apresentar uma única pré-visualização autenticada antes da aplicação ativa. + +**Plano atual: zero inserções e zero eliminações.** Não existe um lote inequívoco para aplicar em QA ou na base ativa. A prova QA confirma que não houve associação automática nem alteração das relações existentes. Não reutilizar o botão de rastreabilidade das 13 referências como se aplicasse estas novas correspondências. + +Matriz e plano completos privados: `opportunity-association-plan.json`. As opções não vão para `opportunity_projects`/`opportunity_clusters` enquanto permanecerem indeterminadas. diff --git a/docs/investmoz/v2/origem-oportunidades-e-separacao.md b/docs/investmoz/v2/origem-oportunidades-e-separacao.md new file mode 100644 index 00000000..ecf739e1 --- /dev/null +++ b/docs/investmoz/v2/origem-oportunidades-e-separacao.md @@ -0,0 +1,45 @@ +# Origem dos seis registos e separação de exemplos + +9 de outubro de 2026. Complementa [as correspondências por oportunidade](oportunidades-correspondencias.md). Não decide pares por setor, localização ou proximidade, nem reabre a aceitação de fontes oficiais identificadas. + +## O que a evidência permite concluir + +Os seis IDs/referências constam, com os mesmos títulos, descrições, províncias, valores, maturidade, financiamento e o texto «Promotor em validação», de `database/seeders/OpportunitySeeder.php`. O ficheiro usa `updateOrCreate` pela referência, define `status=published` e atribui a mesma execução a `published_at`. O código entrou no commit inicial `250ac35`. + +Isto confirma a existência e a correspondência dos registos com os valores do seeder. **Não comprova a existência real dos investimentos, a origem oficial de cada oportunidade ou qual pessoa executou a criação/importação histórica.** A data de criação coincidente dos seis registos é compatível com lote, mas não prova a execução específica do seeder. Também não autoriza concluir, sem decisão do responsável, que todos são fictícios. + +O `DatabaseSeeder` atual não chama `OpportunitySeeder`; chama `ProductionReferenceSeeder` e, em local/testing, outros fixtures. Portanto, a criação destes seis registos não se explica pela cadeia atual do seeder geral. A auditoria disponível deve ser lida junto com o histórico de implantação, sem inventar uma autoria ou uma execução. Prova privada: `opportunity-origin-confirmation.json`. + +A consulta protegida encontrou nove eventos `updated`, de `portal-management`, pela conta 2 em 7 de outubro. Esses eventos documentam edição posterior, não criação nem classificação como investimento real. A conta editora não foi automaticamente nomeada responsável pelas oportunidades. A comparação dos oito campos por registo passou nos seis IDs; nenhuma escrita ativa foi realizada. + +Os textos já extraídos do acervo contêm menções a parte destes registos em exemplos e listagens de código. Esses exemplos fundamentam a origem de conceção do cadastro; não demonstram que uma oportunidade real pertence a uma âncora. Os seis mantêm o estado ativo existente nesta investigação: nenhuma classificação, publicação, descrição ou associação foi alterada. + +## Perguntas a entregar ao responsável + +Para **cada ID 1–6**, confirmar: investimento real, exemplo/demonstração ou origem ainda desconhecida; nome do promotor e identificador do investimento/componente, se disponíveis; documento ou decisão de cadastro que identifica o registo; responsável e data da declaração. Não pedir certificação adicional da credibilidade de uma fonte oficial já aceite. + +| ID | Referência e título | Esclarecimento necessário antes de associar | +| --- | --- | --- | +| 1 | INV-MZ-AGR-001 — Unidade de processamento agrícola em Nampula | A que projeto/polo concreto pertence? Qual a atividade/cultura, unidade e código do componente? | +| 2 | INV-MZ-ENE-002 — Mini-rede solar para polo produtivo | Qual é o polo abastecido, o operador e a mini-rede concreta? Como se identifica o investimento das 40 unidades? | +| 3 | INV-MZ-TUR-003 — Eco-lodge costeiro com integração comunitária | Qual é o empreendimento e a sua pertença concreta a um projeto/polo? Não decidir apenas por Inhambane ou pela atividade turística. | +| 4 | INV-MZ-INF-004 — Centro logístico provincial | Qual a localização efetiva, instalação e corredor servido? Serve um corredor ou vários? | +| 5 | INV-MZ-BLUE-005 — Processamento de pescado e cadeia fria | Integra expressamente A12 ou é uma iniciativa independente? Qual é a unidade/componente que o demonstra? | +| 6 | INV-MZ-FIN-006 — Plataforma de crédito para PME agrícolas | Pertence a um cluster ou presta apenas serviço financeiro aos seus participantes? Qual o instrumento, operador e relação assumida? | + +As alternativas do relatório de correspondências são apenas hipóteses de esclarecimento. Uma relação de serviço financeiro não deve ser gravada como pertença a cluster no pivot atual; exige representação explícita do papel sem alterar a identidade dos participantes. + +Nenhuma mensagem foi enviada ao responsável. O questionário está preparado para entrega pelo proprietário; identidade e canal de contacto não foram presumidos. + +## Plano único de classificação e eventual separação + +1. Registar as respostas por ID e preservar a evidência e a autoria da decisão. Não classificar todos automaticamente por coincidirem com um seeder. +2. Para registos confirmados como reais, preservar publicação/IDs/histórico e preencher apenas as correspondências explicitamente decididas. Não aplicar opções do relatório como associações. +3. Para registos confirmados como exemplos, adicionar uma classificação explícita de conteúdo à entidade existente (`real`, `example`, `undetermined`, a reconciliar em QA com o modelo), mantendo separado o estado editorial. Não criar cópias paralelas nem reutilizar `validation_status` jurídico para classificar exemplos. +4. Apresentar rótulo **«Exemplo de demonstração — não constitui oportunidade real»**, separar do catálogo e métricas reais e permitir consulta em área de demonstração autorizada. Abranger pesquisa, API, mapa, filtros e fichas, incluindo URLs antigas; esconder apenas da pesquisa sem corrigir acesso direto seria insuficiente. +5. Preservar IDs, referências, ficheiros, mensagens, manifestações de interesse, favoritos, notificações, tarefas e relações existentes. Não eliminar, reseedar, substituir títulos ou apagar eventos. Solicitações históricas não se convertem em exemplos; identificar o contexto original e encaminhar a conferência humana. +6. Preparar uma única pré-visualização por ID: classificação anterior/proposta, fundamento, efeito na divulgação e nas métricas, dependências e valores para reversão. Implementar somente em QA antes da aplicação autenticada. +7. Testar catálogo misto, filtros/mapa/API, acesso direto, permissões da demonstração, idempotência, histórico e recuperação/reversão. Não aplicar um lote sem classificação individual decidida. +8. Antes de alteração ativa, atualizar backup/restauro, compatibilidade e ensaio de reversão; executar pelo fluxo autenticado existente. A reversão restaura classificação/divulgação anteriores sem apagar a decisão ou o histórico. + +**Estado atualizado:** origem de código constatada; realidade/classificação e pares pendentes do responsável. A revisão autenticada e separação condicional foram implementadas e testadas em QA, sem aplicação ativa. A classificação utiliza eventos da auditoria existente, mantendo o estado editorial e dispensando nova coluna/tabela. Ver [implementação, provas e pré-visualização](revisao-origem-oportunidades.md). Questões jurídicas específicas, cobertura do inventário, ClamD e primeiro pedido real continuam independentes. diff --git a/docs/investmoz/v2/qualidade-territorial.md b/docs/investmoz/v2/qualidade-territorial.md index 9013cb5b..97a01428 100644 --- a/docs/investmoz/v2/qualidade-territorial.md +++ b/docs/investmoz/v2/qualidade-territorial.md @@ -94,3 +94,11 @@ Diferença de cobertura do inventário documental, questões jurídicas específ [Plano concreto de aplicação autenticada](ativacao-rastreabilidade-territorial.md): 112 entradas de fonte/fundamento para os mesmos 46 registos; 13 referências de relações cluster–projeto propostas, sem alterar validações ou criar ligações por proximidade. Os 28 vínculos territoriais existentes, as 11 referências oficiais/legais, as 14 fichas validadas e o pacote INE aceite foram reutilizados. Pendências de rastreabilidade preservam a aceitação existente. Backup/restauro de 288 tabelas e cinco ficheiros verificado; aplicação e reversão testadas na cópia isolada. 56 testes PHP/884 asserções passaram e o navegador comprovou o clique V2, resposta HTTP, recuperação, fonte por associação na ficha e reversão. Nenhuma proposta deste incremento foi aplicada à base ativa. A pré-visualização autenticada mostra campos atuais/propostos, fonte, motivo e efeito antes da ação do responsável. + +## Conferência de todas as associações do plano do proprietário + +9 de outubro de 2026. A comparação por identificador do projeto, território e papel confirma que **as 28 associações territoriais expressas no plano original já estão cadastradas na base ativa**. As 13 relações cluster–projeto expressas nesse plano também existem. Nenhuma destas 41 associações está ausente; não é necessária nova fonte nem nova aplicação para duplicá-las. A última aplicação V2 acrescentou referências às 13 relações existentes; não criou as 28 localizações, que já estavam aplicadas anteriormente. + +A resposta anterior que dizia apenas «não foram acrescentadas localizações nesta aplicação» descrevia o incremento V2, mas era incompleta quanto ao estado global. As localizações solicitadas naquele plano estão presentes, com os papéis `primary`, `secondary` e `impact` originais. Prova privada, por associação: `owner-associations-confirmation.json`, obtida exclusivamente por leitura. + +As seis oportunidades genéricas existentes não contêm pares explícitos de correspondência com âncoras/clusters no plano examinado. Identificar esse par é uma questão de correspondência entre registos, não de revalidação da fonte oficial. Os 158 componentes preparados no plano original não são 158 oportunidades já cadastradas: 86 têm código explícito e 72 apresentam conflito entre código e legenda. Criar esses registos ou resolver os conflitos é trabalho distinto de aplicar novamente as 41 associações já existentes. Não se convertem automaticamente os componentes em ligações às seis oportunidades genéricas. diff --git a/docs/investmoz/v2/revisao-origem-oportunidades.md b/docs/investmoz/v2/revisao-origem-oportunidades.md new file mode 100644 index 00000000..9fe3e136 --- /dev/null +++ b/docs/investmoz/v2/revisao-origem-oportunidades.md @@ -0,0 +1,37 @@ +# Revisão da origem das seis oportunidades + +9 de outubro de 2026. Complementa [a investigação de origem](origem-oportunidades-e-separacao.md) e [as correspondências individuais](oportunidades-correspondencias.md). + +## Evidência encontrada + +Os seis IDs 1–6 coincidem nos oito campos comparados com `database/seeders/OpportunitySeeder.php`, introduzido no commit `250ac35`. O histórico consultado não identificou chamada desse seeder na cadeia de `DatabaseSeeder`. A coincidência demonstra correspondência com a definição de código, mas não comprova execução histórica do seeder nem classifica os investimentos como reais ou fictícios. + +A auditoria ativa contém nove edições posteriores pela conta 2, em 7 de outubro, sem evento de criação recuperado nesta pesquisa. A pesquisa de tabelas com nome relacionado com importação/seed e dos recibos privados consultados não encontrou prova adicional de importação. Este resultado é limitado ao histórico e artefactos consultados; ausência de prova não determina classificação. + +Nas extrações de 35 documentos e nos 13 433 parágrafos do mestre relido, os registos 1–5 aparecem em exemplos/listagens de código. Não foi recuperada menção exata ao sexto nem correspondência inequívoca com âncoras/clusters. A aceitação das fontes oficiais existentes permanece intacta. A classificação e a associação territorial são decisões distintas. + +## Revisão autenticada preparada + +Endereço: **http://127.0.0.1:8000/management/opportunity-origin**, sujeito à autenticação e permissão de gestão existentes. + +Cada cartão apresenta ID, referência, título, descrição, classificação e histórico. O responsável escolhe **oportunidade real**, **exemplo demonstrativo** ou **origem ainda indeterminada**, indica fundamento e executa **Pré-visualizar decisão**. A pré-visualização mostra o estado anterior, proposta e efeito. Só depois de marcar **Confirmo esta decisão de classificação** fica disponível **Aplicar decisão de classificação**, com prova válida de backup/restauro/reversão. + +Sem decisão registada, o estado é indeterminado; isso não modifica a publicação editorial existente. A aplicação acrescenta um evento ao mecanismo de auditoria existente, com autor autenticado, data e fundamento. Não altera associações, IDs, descrições ou estados jurídicos. A classificação utiliza esses eventos, sem nova tabela ou migration. Pedidos repetidos são idempotentes; alterações concorrentes exigem nova pré-visualização. + +Uma decisão de exemplo separa o item do catálogo público, pesquisa, API, mapa e acesso direto público. A gestão conserva consulta e rótulo explícito de demonstração. A reversão é uma nova decisão explícita, preservando ambas as decisões e as interações originais. Esta revisão não cria relações projeto/cluster nem converte pedidos históricos em exemplos. + +## QA e preservação + +- Testes de regressão: **58 testes, 917 asserções, todos passaram**, exclusivamente em QA isolada. +- Navegador: pré-visualização sem aplicação; confirmação explícita; separação de exemplo na API/mapa; consulta autenticada; reposição; nenhum erro JavaScript observado. +- Restauro atualizado: **288 tabelas e cinco ficheiros** verificados na cópia `desk_restore_20261009_003841_test:55441`. +- Após o ensaio, hashes de dez tabelas comparados com o backup: oportunidades, duas relações de oportunidade, favoritos, pedidos de interesse, recibos, mensagens, tarefas, localizações de projetos e relações cluster/projeto preservados. Os dois eventos de decisão do ensaio permanecem apenas em QA. +- O teste funcional inclui relações e favorito de fixture, conferindo preservação após separação e reversão. Fixtures não foram inseridas na base ativa. + +Provas privadas: `opportunity-origin-confirmation.json`, `opportunity-origin-backup-proof.json`, `opportunity-origin-browser.json`, `opportunity-origin-reversal-evidence.json` e `opportunity-origin-proof.json`, em `storage/app/private/investmoz-v2`. Não publicar backup, configuração ou chave. A prova de ativação corresponde à versão ensaiada e expira em **10/10/2026 às 02:38:49 locais**; após isso é necessário atualizar backup/restauro antes de aplicar decisões. + +## Estado ativo e pendências + +Na leitura protegida desta entrega há **zero decisões de classificação** na base ativa. Nenhum dos seis foi classificado, retirado do catálogo ou associado automaticamente. A página está preparada para revisão individual pelo responsável, começando pela pré-visualização. Não foi enviado contacto externo. + +Continuam separadas: decisões sobre realidade dos investimentos e correspondências ambíguas; diferença de cobertura do inventário; questões jurídicas específicas; ClamD real; fontes institucionais pendentes específicas; primeiro pedido real no Balcão. Estes testes não concluem nenhuma dessas pendências. diff --git a/resources/js/pages/management/opportunity-origin.tsx b/resources/js/pages/management/opportunity-origin.tsx new file mode 100644 index 00000000..18e19f20 --- /dev/null +++ b/resources/js/pages/management/opportunity-origin.tsx @@ -0,0 +1,233 @@ +import { router } from '@inertiajs/react'; +import { useState } from 'react'; +import { Management } from './shared'; + +type RecordState = { + id: number; + reference: string; + title: string; + description: string; + classification: string; + snapshot_hash: string; + history: { + id: string; + classification: string; + reason: string; + author: number; + date: string; + }[]; +}; +const labels: Record = { + real: 'Oportunidade real', + example: 'Exemplo demonstrativo', + undetermined: 'Origem ainda indeterminada', +}; +type Preview = { + current: RecordState; + classification: string; + reason: string; + effect: string; + ready: boolean; +}; +function Review({ record, ready }: { record: RecordState; ready: boolean }) { + const [classification, setClassification] = useState(record.classification); + const [reason, setReason] = useState(''); + const [preview, setPreview] = useState(null); + const [error, setError] = useState(''); + const [processing, setProcessing] = useState(false); + const [confirmed, setConfirmed] = useState(false); + const [result, setResult] = useState(''); + async function request(apply: boolean) { + setProcessing(true); + setError(''); + try { + const xsrf = document.cookie + .split('; ') + .find((cookie) => cookie.startsWith('XSRF-TOKEN=')) + ?.split('=') + .slice(1) + .join('='); + const response = await fetch( + '/management/opportunity-origin/' + + (apply ? 'apply' : 'preview'), + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + 'X-XSRF-TOKEN': decodeURIComponent(xsrf ?? ''), + }, + body: JSON.stringify({ + id: record.id, + classification, + reason, + snapshot_hash: preview?.current.snapshot_hash, + confirm: confirmed, + }), + }, + ); + const data = await response.json(); + if (!response.ok) + throw new Error( + Object.values(data.errors ?? {}) + .flat() + .join(' ') || + data.message || + 'Operação recusada.', + ); + if (apply) { + setPreview(null); + setResult( + 'Decisão registada. Classificação separada das associações; histórico preservado.', + ); + router.reload({ only: ['records', 'ready'] }); + } else { + setPreview(data); + setConfirmed(false); + } + } catch (exception) { + setError( + exception instanceof Error + ? exception.message + : 'Falha de comunicação; dados preservados.', + ); + } finally { + setProcessing(false); + } + } + return ( +
+

+ ID {record.id} — {record.title} +

+

{record.reference}

+

{record.description}

+

+ Classificação atual: {labels[record.classification]}.{' '} + {record.classification === 'example' && + 'Exemplo de demonstração — não constitui oportunidade real. Consulta autenticada preservada.'} +

+ +