Files
Investidor-V2/tools/portal-audit-review.cjs
T
nala 6ddafc95ba
tests / ci (push) Canceled after 0s
oportunidades 4
2026-10-10 07:03:59 +02:00

1397 lines
66 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Reconcile the initial register with reviewed evidence; no database writes.
const fs = require('node:fs'),
path = require('node:path');
const root = path.resolve(__dirname, '..'),
priv = path.join(root, 'storage/app/private/investmoz-v2/portal-audit'),
out = path.join(root, 'docs/investmoz/v2/auditoria-portal');
let rows = JSON.parse(fs.readFileSync(path.join(priv, 'matriz.json'), 'utf8'));
const inventory = JSON.parse(
fs.readFileSync(path.join(priv, 'inventario-fontes.json'), 'utf8'),
);
// Spreadsheet consumers must not execute clauses as formulas. Raw text stays in JSON.
const csvCell = (value) => {
const raw = String(value ?? '');
return (
'"' + (/^[=+@-]/.test(raw) ? "'" + raw : raw).replace(/"/g, '""') + '"'
);
};
const active = JSON.parse(
fs.readFileSync(path.join(priv, 'estado-ativo-after.json'), 'utf8'),
);
const before = JSON.parse(
fs.readFileSync(path.join(priv, 'estado-ativo-before.json'), 'utf8'),
);
const routes = JSON.parse(
fs.readFileSync(path.join(priv, 'ensaios-rotas.json'), 'utf8'),
);
const suiteNames = {
oportunidades: [
'OpportunityManagementTest',
'OpportunityResourcesTest',
'OpportunityOriginReviewTest',
],
'cartografia/relações': [
'InvestmentTerritoryTest',
'TerritorialRegistryTest',
'InvestmentTerritorialTraceabilityTest',
'InvestmentZoneTest',
],
'Balcão/orientação': ['InvestorGuidanceTest'],
'CRM/solicitações': [
'InterestSubmissionTest',
'InvestmentWorkspaceTest',
'PortalOriginCompatibilityTest',
],
simuladores: [
'SimulationExecutionTest',
'SimulationFormDraftTest',
'SimulationDraftContinuityTest',
'SimulationTaxRuleGovernanceTest',
],
biblioteca: [
'PortalServicesTest',
'DocumentLibrarySchemaCompatibilityTest',
'ImportDocumentBatchTest',
'ClamDScannerIntegrationTest',
],
educação: ['EducationTest', 'SkillsEducationSchemaTest'],
'Bolsa/parcerias': ['PartnershipExchangeTest'],
negócios: [
'ExistingBusinessFoundationTest',
'ExistingBusinessIntakeTest',
'ExistingBusinessWizardTest',
],
'dashboard/perfis': ['InvestmentWorkspaceTest', 'InvestorGuidanceTest'],
};
const tablesByDomain = {
oportunidades: [
'opportunities',
'opportunity_images',
'opportunity_documents',
'opportunity_location_drafts',
],
'cartografia/relações': [
'geo_locations',
'geo_geometries',
'project_locations',
'cluster_projects',
'opportunity_projects',
'opportunity_clusters',
],
'Balcão/orientação': [
'desk_cases',
'desk_requests',
'desk_institutional_links',
'desk_messages',
],
'CRM/solicitações': ['interest_requests', 'leads', 'investors'],
simuladores: [
'simulation_form_drafts',
'simulation_scenarios',
'simulations',
'simulation_tax_rules',
],
biblioteca: ['documents', 'portal_resources', 'portal_resource_links'],
educação: [
'labour_demands',
'training_capacities',
'education_opportunities',
'education_investment_proposals',
],
'Bolsa/parcerias': [
'portal_partnerships',
'partnership_rooms',
'partnership_interests',
],
negócios: ['existing_businesses', 'existing_business_revisions'],
'dashboard/perfis': [
'investment_accounts',
'investment_company_declarations',
'investment_submissions',
],
};
for (const r of rows) {
if (r.id.startsWith('OP-')) r.modulo = 'oportunidades';
if (/^(RF-EDU|RNF-EDU)/.test(r.id)) r.modulo = 'educação';
if (
/país|origem declar|estatístic|dropdown|cartograf|geometri|coordena|território|recupera|rascunho/i.test(
r.pedido,
)
)
r.prioridade = 'P1';
const tables = tablesByDomain[r.modulo] || [];
r.ativo =
tables
.map(
(t) =>
`public.${t}: ${active.tables['public.' + t] ? active.tables['public.' + t].rows + ' registos' : 'tabela ausente'}`,
)
.join('; ') ||
'Requisito transversal; consultar estado-ativo-after.json';
r.evidencia =
(suiteNames[r.modulo] || []).join(', ') +
'; suite global QA e GET por perfil disponíveis. Evidência do domínio não demonstra integralmente esta cláusula; UI individual pendente.';
r.lacuna =
'Verificar individualmente este critério no contrato e percurso aplicável; a matriz inicial conserva texto/fonte sem o promover a aceitação.';
r.dependencia =
'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.';
if (
/^OP-FIELD-(realEstate|landRights|businessTransaction|parcel|improvement|community)/.test(
r.id,
)
) {
r.estado = 'não implementado';
r.lacuna =
'Contrato especializado no cadastro integrado de oportunidades ainda não implementado; módulos de negócios/fundiário existentes não preenchem automaticamente este contrato.';
r.prioridade = 'P1';
r.dependencia =
'Reconciliar objetos e direitos canónicos existentes com o contrato; dados ausentes permanecem pendentes.';
}
if (
/^OP-FIELD-(location|document|image)(-|Create-|Patch-|Response-)/.test(
r.id,
)
) {
r.estado = 'parcial';
r.lacuna =
'Coleções de recursos privadas preparadas em QA; esquema ativo ausente. Correspondência integral deste campo com o contrato ainda não demonstrada.';
r.prioridade = 'P1';
r.dependencia =
'Plano autenticado de recursos; scan por binário e GD para derivadas, sem bloquear metadados.';
}
if (r.id.startsWith('AUD-FIELD-')) {
r.prioridade = 'P1';
r.implementacao = r.pedido.replace(
/^Pesquisar, selecionar, guardar e recuperar no consumidor /,
'',
);
r.lacuna =
'Inspeção visual individual ainda não executada; inventário de chamada não demonstra pesquisa/recuperação/uso cartográfico.';
}
}
const items = [
[
'AUD-COUNTRY-01',
'P1',
'parcial',
'País de residência no perfil',
'dashboard/perfis',
'/investidor/profile',
'CountryCatalogue; CountrySelect; InvestmentWorkspaceController',
'Nova sessão autenticada recupera ISO guardado; invalidar ZZ sem mudar versão',
'QA InvestmentWorkspaceTest; prova histórica functional-country-browser.json',
'Restantes perfis e cadastro inicial precisam de reconciliação individual',
'Nenhuma decisão; reutilizar payload cifrado existente',
],
[
'AUD-COUNTRY-02',
'P1',
'parcial',
'País de constituição da entidade',
'dashboard/perfis',
'/investidor/profile; POST /investidor/companies/{id?}',
'InvestmentWorkspaceController; investment_company_declarations',
'Guardar ISO distinto de residência; negar outra conta; recuperar versão',
'QA InvestmentWorkspaceTest',
'Percurso completo de UI pendente',
'Nenhuma decisão; mandato não é concedido pelo país',
],
[
'AUD-COUNTRY-03',
'P1',
'parcial',
'Origem declarada em manifestações e pedidos do Balcão',
'CRM/solicitações',
'/investidor/manifestacoes/{id}; /balcao-virtual/pedidos/{id}',
'InvestmentSubmissionController; VirtualDesk',
'Residência/constituição diferentes da origem; 422 preserva rascunho',
'QA InvestmentWorkspaceTest; InvestorGuidanceTest',
'UI completa com os novos campos ainda pendente',
'Nenhuma inferência de IP/residência',
],
[
'AUD-COUNTRY-04',
'P1',
'parcial',
'Origem declarada nos formulários públicos e legados',
'CRM/solicitações',
'/apoio; /api/v1/interest-requests; /investidor/{id}/interesse; /negocios/{slug}/manifestar-interesse',
'PortalServicesController; InterestRequestController; InvestorWorkspaceController; componentes ISO',
'PT/ZA guardados em metadata; missing continua não informado; ZZ 422; replay sem duplicar; origem alterada com chave igual 409',
'Correção QA: PortalOriginCompatibilityTest; InvestmentPortfolioTest; InterestSubmissionTest',
'Código preparado; UI pendente. Campo genérico country ainda legado em alguns formulários',
'Nenhuma migration; usar interest_requests.metadata e recibos existentes',
],
[
'AUD-STATS-01',
'P1',
'parcial',
'Estatísticas internas por origem, incluindo filtro e não informado',
'CRM/solicitações',
'/management/crm/leads',
'DeclaredOriginStatistics; CrmController',
'País/unknown/todas; conjuntos separados; manter legados desconhecidos; 403 sem autorização',
'QA InvestmentWorkspaceTest; PortalOriginCompatibilityTest',
'UI do filtro pendente; não são investidores únicos',
'Origem explicitamente declarada; sem exportação de contactos',
],
[
'AUD-GEO-01',
'P1',
'parcial',
'Pesquisa e recuperação no seletor provincial partilhado',
'cartografia/relações',
'Consumidores de ProvinceSelect em consumidores-campos.json',
'ProvinceSelect; InvestmentTerritoryController::options',
'Pesquisar nome/código; cancelar resposta antiga; conservar valor guardado fora das opções sem convertê-lo',
'TypeScript/build; API e hierarquia na suite territorial',
'Correção de UI preparada; ensaio visual ainda pendente',
'Nenhuma decisão; usar o cadastro geo_locations',
],
[
'AUD-GEO-02',
'P1',
'parcial',
'Hierarquia pesquisável em todos os campos territoriais',
'cartografia/relações',
'Todos os AUD-FIELD-*; /balcao-virtual/pedidos/{id}',
'TerritoryFields; TerritoryLevel; seletores específicos',
'Troca de pai limpa descendentes; guardar/recuperar IDs; erros visíveis; sem inventar localidades',
'Tests/frontend/territory-path; ensaios API territoriais',
'Campo plano do Balcão, seletores próprios e mapas exigem demonstração individual',
'Cadastro existente; não inferir ponto do centro provincial',
],
[
'AUD-GEO-03',
'P1',
'parcial',
'Cartografia de todas as entidades e campos',
'cartografia/relações',
'/oportunidades?view=map; /zonas; /carteira; campos de localização',
'InvestmentDiscoveryController; maps; geo_geometries',
'Mostrar geometrias cadastradas e lista dos não localizados, sem pontos inventados',
'Leitura ativa: geo_geometries=0; fichas/mapa com prova histórica',
'Nenhuma geometria ativa; lista não certifica cartografia completa',
'Ficheiros/coordenadas/limites identificados no acervo aceite; conferir âmbito sem revalidar credibilidade',
],
[
'AUD-REL-01',
'P1',
'parcial',
'Preservar relações territoriais e citar por associação',
'cartografia/relações',
'/management/relationship-repair; fichas de projetos/clusters',
'InvestmentRelationshipRepair; InvestmentTerritorialTraceability',
'28 locais e 13 relações mantidos; citações e papéis conservados; conflito/idempotência/reversão',
'Leitura ativa e suite territorial; confirmação histórica V2',
'Pontes de rastreabilidade em falta; não inferir pares oportunidade–projeto/cluster',
'Acervo aceite; recuperar citação de cada par',
],
[
'AUD-REL-02',
'P1',
'dependente de decisão concreta',
'Correspondência das seis oportunidades com âncoras/clusters',
'cartografia/relações',
'/management/opportunities/{id}/context',
'oportunidades-correspondencias.md; opportunity_projects/opportunity_clusters',
'Somente pares explicitamente identificados pelo responsável/documento; sem duplicar relações',
'Leitura ativa: ambas relações=0',
'Identidade dos pares continua indeterminada',
'Pergunta: para cada ID 1–6, qual projeto/cluster exato deve ser ligado, com que papel e referência? Não impede outras correções',
],
[
'AUD-OP-01',
'P1',
'parcial',
'Guardar e recuperar rascunho incompleto; exclusão/arquivo/restauro',
'oportunidades',
'/management/opportunities; /management/opportunities/{id}/edit; PUT /management/opportunities/{id}/draft',
'OpportunityManagement; opportunities; audit_events',
'Rascunho incompleto no servidor; ETag/409; motivo; restore draft sem republicar',
'QA OpportunityManagementTest; prova histórica navegador',
'Gestão básica instalada; nenhuma aceitação integral do schema',
'Sem nova instalação da gestão básica',
],
[
'AUD-OP-02',
'P1',
'implementado mas não ativado',
'Recursos privados: imagens/documentos/multilocalização',
'oportunidades',
'/management/opportunities/{id}/resources; /management/opportunity-resources-install',
'OpportunityResources; documents; geo_locations',
'Instalação auditada uma vez; originais/versões preservados; até 50 locais; exclusão/restauro; sem publicação automática',
'QA OpportunityResourcesTest; migration/reversão em cópia restaurada',
'Esquema de recursos ausente na base ativa; UI corrente pendente',
'Ação autenticada, backup/restauro e prova do código atual',
],
[
'AUD-OP-03',
'P1',
'não implementado',
'Workflow integral segregado de oportunidades',
'oportunidades',
'Gestão de oportunidades, §20 da especificação',
'Reutilizar gate manage-portal, management_draft e auditoria; não copiar workflow de outro domínio',
'Submeter/devolver/aprovar/publicar versão completa; nenhum autor autoaprova; público conserva versão aprovada',
'Lacuna expressa nos relatórios e ausência destas transições no cadastro integrado',
'Aprovação/publicação integral ainda por construir',
'Definir designações reais para segregar atos; desenvolver rascunhos/validação antes da designação',
],
[
'AUD-OP-04',
'P1',
'não implementado',
'Contratos especializados imobiliário, DUAT e transações',
'oportunidades',
'§24–28; OP-FIELD-* especializados',
'Reutilizar ExistingBusiness, direitos/objetos e LandAssistance; Opportunity',
'Campos/coleções específicos da categoria persistem e validam com vínculo canónico',
'Contrato de 110 definições inventariado; integrações especializadas ausentes no formulário',
'Não duplicar cadastros nem tomar simples seleção da categoria como módulo concluído',
'Dados concretos do objeto/direito/mandato; nenhuma credibilidade oficial reaberta',
],
[
'AUD-DASH-01',
'P1',
'parcial',
'Dashboard integrado por perfil com ações úteis',
'dashboard/perfis',
'/dashboard; /management/opportunities; /balcao-virtual/painel',
'DashboardController; PortalMenu; gestão existente',
'Ações permitidas por perfil; foco/teclado/móvel; indicadores e destinos reais',
'QA GETs por perfil e provas históricas de navegação',
'Contadores/workflow avançados e inventário visual de todos os botões pendentes',
'Não atribuir permissões ativas para ensaiar',
],
[
'AUD-DESK-01',
'P1',
'parcial',
'Criar/editar/recuperar dossier e pedido com feedback',
'Balcão/orientação',
'/balcao-virtual/painel; /balcao-virtual/dossiers/{id}; /balcao-virtual/pedidos/{id}',
'VirtualDesk; desk_cases/requests/messages; tarefas existentes',
'Criar uma vez, guardar versão, recuperar em sessão nova, corrigir 422, transições e mensagem explícitas',
'QA InvestorGuidanceTest; provas históricas browser',
'Ativo: 1 dossier e 0 pedidos; primeiro pedido real não demonstrado',
'Serviço interno real e dono/responsável; primeiro pedido só com dados do utilizador',
],
[
'AUD-DESK-02',
'P1',
'parcial',
'Reutilizar investimento, percurso, pedido e responsável no Balcão',
'Balcão/orientação',
'/balcao-virtual/painel',
'Contextos da conta; investor_journeys; interest_requests',
'Título corrigido conservado; não copiar pedido/conta; outra conta negada',
'QA InvestorGuidanceTest',
'Percurso visual completo com reutilização pendente',
'Nenhuma decisão; referências por ID e consentimento existentes',
],
[
'AUD-CRM-01',
'P1',
'parcial',
'Converter e acompanhar internamente com responsável/próxima ação',
'CRM/solicitações',
'/management/interests; /management/crm/leads; /management/crm/leads/{lead}',
'CrmController; ConvertInterestToLead; Lead/CrmTask',
'Converter sem duplicar investidor; responsável autorizado; próxima tarefa antes de encerrar última',
'QA CRM/Portfolio e GET por gestor',
'Ativo: 0 leads e 0 negociações; operação real não demonstrada',
'Responsáveis existentes; SuiteCRM externo é independente e desligado',
],
[
'AUD-GUIDE-01',
'P1',
'parcial',
'Orientação explicável e evidência com âmbito/período',
'Balcão/orientação',
'/balcao-virtual/dossiers/{id}/orientacao',
'InvestorGuidance; InvestorGuidanceRules; evidência por hash',
'Mostrar contradição/lacuna sem bloquear preparação; evidência aplicável com autoria/data; não homologar',
'QA regras e InvestorGuidanceTest',
'Consistência e fontes por serviço ainda têm lacunas concretas',
'Unidade/período/moeda declarados; IA externa não obrigatória',
],
[
'AUD-SIM-01',
'P1',
'parcial',
'Rascunhos do simulador e recuperação privada',
'simuladores',
'/simulador-fiscal; APIs de simulation-form-drafts',
'SimulationFormDraft; SimulationForm; storage cifrado',
'Guardar privado no servidor; nova sessão recupera; anónimo não envia PII; 422 conserva campos',
'QA SimulationFormDraftTest; SimulationDraftContinuityTest; 152 testes frontend',
'Nova inspeção visual transversal pendente',
'Nenhuma ativação fiscal por rascunho/documento',
],
[
'AUD-SIM-02',
'P1',
'parcial',
'Cálculos financeiros e fiscais separados e rastreáveis',
'simuladores',
'/simulador-fiscal; /api/v1/simulations',
'SimulationExecution; SimulationTaxParameterResolver; regras aprovadas',
'Totais e fluxo reconcilam; parâmetro ausente indeterminado; períodos/versões distintos',
'QA SimulationExecutionTest; SimulationTaxRuleGovernanceTest; testes frontend',
'Cobertura jurídica por diploma/artigo e parâmetros concretos pendente',
'Conferir período/alterações/âmbito na fonte oficial aceite; nunca substituir por zero ou taxa mais recente',
],
[
'AUD-LIB-01',
'P1',
'parcial',
'Inventariar e consultar todo o acervo autorizado',
'biblioteca',
'/documentos; /legislacao; /management/portal-services',
'PortalServicesController; documents/portal_resources; inventário privado',
'Disponibilidade/documentação/uso em cálculo separados; publicação não exige relação a módulo',
'Inventário atual; QA biblioteca; leitura ativa 4 documentos',
'253 ficheiros externos + 60 anexos embebidos inventariados não equivalem a biblioteca publicada completa',
'Autorização por ficheiro; bloquear apenas impedimento concreto',
],
[
'AUD-LIB-02',
'P1',
'parcial',
'Importação privada em lote idempotente com pré-visualização',
'biblioteca',
'Comandos e gestão da biblioteca',
'documents; checksum; scan; auditoria existentes',
'Hash/dedup bytes/conflito identidade/versão; escrita privada; scan; nunca publicar lote automaticamente',
'QA biblioteca/scan; inventário com hash',
'Cobertura de importação de todos os ficheiros/anexos ainda pendente',
'Manifesto de divulgação e impedimentos por ficheiro; sem nova certificação da fonte',
],
[
'AUD-SCAN-01',
'P1',
'com falha',
'Scanner ClamD real e derivados seguros',
'biblioteca',
'malware:health; recursos/downloads',
'MalwareScanner; ScanDocumentForMalware; OpportunityImageProcessor',
'Scanner disponível com versão/assinaturas; EICAR isolado; binário/hash limpo; scan próprio de derivada',
'Três testes reais saltados; metadados/rascunhos conservados',
'ClamD real indisponível; GD web ainda por verificar',
'Serviço/configuração reais de ClamD; GD/WebP no PHP web, sem usar fixture para ficheiro real',
],
[
'AUD-EDU-01',
'P1',
'parcial',
'Educação: investimento e procura/capacidade com rascunhos por ator',
'educação',
'/educacao; /management/education; APIs skills-edu',
'EducationController; EducationInvestmentController; schema skills_edu existente',
'Criar/editar/recuperar; distinguir vagas/trabalhadores; acesso e fonte; sem autoacreditação',
'QA EducationTest/SkillsEducationSchemaTest; GETs; schema skills_edu ativo vazio',
'Fluxos completos por ator e dados institucionais não demonstrados; não confundir catálogo e submódulo',
'Reutilizar entidades e configurações existentes; mandatos só para atos institucionais',
],
[
'AUD-PART-01',
'P2',
'parcial',
'Bolsa: rascunhos, candidaturas, sala e marcos',
'Bolsa/parcerias',
'/parcerias; /parcerias/{id}; sala',
'PartnershipExchange; portal_partnerships; messages/rooms',
'Consentimento, versões, candidatura isolada, retiro, mensagens, acordo/marcos com papéis reais',
'QA PartnershipExchangeTest; instalação ativa confirmada',
'Cadastro ativo vazio; percurso real e acordos externos ausentes',
'Reutilizar conta/organização/documento; acordo não é assinatura certificada',
],
[
'AUD-BIZ-01',
'P2',
'parcial',
'Negócios existentes, direitos/mandatos e publicação',
'negócios',
'/negocios; /investidor/negocios; /management/negocios',
'ExistingBusiness; revisions; documents; policy MFA',
'Guardar/recuperar/conflito; revisar por ator designado; público omite privados',
'QA ExistingBusiness e GETs com MFA; ativo 2 negócios',
'Direitos/mandatos/documentos concretos vazios; UI integral pendente',
'Mandatos e dados jurídicos do objeto; nenhuma aprovação institucional inferida',
],
[
'AUD-LAND-01',
'P2',
'parcial',
'Assistência fundiária sem venda automática de direito',
'oportunidades',
'/terra-duat; /investidor/terra-duat; /management/terra-duat',
'LandAssistanceController; documents; geo_locations',
'Rascunho incompleto; fonte/estado jurídico separado; escopo e transições verificáveis',
'QA LandAssistance; rastreabilidade documental',
'Percurso visual integral e dados concretos pendentes',
'Objetos/direitos identificados; sem inferir legitimidade por fonte genérica',
],
[
'AUD-OPS-01',
'P0',
'parcial',
'Instalação consolidada com backup/restauro e hash atual',
'governação/outros',
'/management/opportunity-resources-install',
'OpportunityResourceInstallation; desk-activation-backup',
'Prova do código/destino atual; 288 tabelas/5 ficheiros restaurados; up/down QA; ação autenticada única',
'Backup de 09/10/2026 15:16:03 UTC; restauro/reversão em desk_restore_20261009_151551_test:55441; preview ready=true',
'Recursos por instalar; prova atual renovada. UI corrente ainda pendente. Não repetir Balcão/Bolsa/gestão',
'Fluxo autenticado; validade de 24 h e hash atual; reversão depois de uso exige reconciliação',
],
[
'AUD-OPS-02',
'P0',
'concluído e demonstrado',
'Excluir execução simultânea de suites sobre a mesma QA',
'governação/outros',
'tests/TestCase.php',
'Guarda de alvo + flock por host/porta/base antes da migração',
'Segundo runner recusado antes de conexão/limpeza; suite sequencial passa; bases diferentes independentes',
'Segundo runner recusado antes de 0 asserções; repetição sequencial: 368 passaram, 3 ClamD saltados, 4401 asserções',
'Correção do tooling QA demonstrada; não é falha do portal nem aceitação funcional completa',
'Executável sem decisão humana; nenhum teste na base ativa',
],
[
'AUD-OPS-03',
'P0',
'concluído e demonstrado',
'Leitura ativa e ensaios QA preservam dados/histórico',
'governação/outros',
'tools/portal-audit-active.php',
'Transação READ ONLY; hashes; snapshots privados; guardas QA',
'Leitor READ ONLY; hashes de negócio/histórico preservados; alterações técnicas cache/sessions explicitadas; restauro verifica 288 tabelas',
'estado-ativo-before/after; backup-proof e comparação de preservação',
'Comparação temporal: 207 de 209 hashes iguais; cache/sessions diferem, causa não atribuída. Backup cobre todas as 288',
'Manter configurações/dumps privados; leituras não autorizam restauro sobre novas escritas',
],
[
'AUD-HUMAN-01',
'P2',
'dependente de decisão concreta',
'Identidade do operador, termos e contactos reais',
'governação/outros',
'Portal público, V2-02/V2-14',
'Configuração existente',
'Publicar identidade/contactos/termos fornecidos pelo responsável; nunca inventar operador',
'Relatórios V2 mantêm a falta concreta',
'Operador/termos/contactos não confirmados nesta auditoria',
'Pergunta: qual a entidade operadora, contactos e versões de termos/política autorizadas para divulgação?',
],
[
'AUD-HUMAN-02',
'P2',
'dependente de decisão concreta',
'Links e serviços institucionais concretos',
'Balcão/orientação',
'/management/balcao/fontes; etapas da jornada',
'InstitutionalLinkMonitor; desk_institutional_links',
'Link/contacto por serviço com conferência de conteúdo e âmbito; fallback; não declarar disponibilidade sem evidência',
'QA de expiração/fallback; ativo 0 links',
'Dados de ligação e entidade/responsável ausentes',
'Pergunta: quais contactos/links específicos e responsável real devem constar por serviço? Não pede nova validação de fonte oficial',
],
[
'AUD-HUMAN-03',
'P2',
'dependente de decisão concreta',
'Modelo multicritério de sucesso/indicadores e pilotos',
'governação/outros',
'V2-09/V2-10/V2-14',
'Regras determinísticas/configurações existentes',
'Pesos, período, limitações e revisão declarados; sem garantia automática',
'Propostas conservadas nos mestres',
'Modelo e baseline/pilotos não aprovados/medidos',
'Pergunta: que pesos, limiares, baseline e responsáveis autorizados devem ser usados nos pilotos?',
],
[
'AUD-UX-01',
'P2',
'parcial',
'Remover UUID interno sem utilidade no cartão da jornada',
'dashboard/perfis',
'/investidor',
'resources/js/pages/portfolio/workspace.tsx; referências internas preservadas',
'Cartão identifica jornada pelo título e ações; gravação e relações continuam pelo mesmo ID',
'Código preparado; TypeScript/build passaram; ensaio visual pendente',
'UUID retirado da apresentação; restantes informações desnecessárias exigem percurso visual',
'Nenhuma alteração de dados',
],
[
'AUD-STYLE-01',
'P2',
'com falha',
'Checks globais de formatação',
'governação/outros',
'npm.cmd run check; php vendor/bin/pint --test',
'Formatadores existentes; corrigir por ficheiro sem alteração semântica',
'Checks globais passam; diff preserva comportamento e conteúdo documental',
'Check final: 17 ficheiros frontend/documentos/scripts preexistentes; Pint global: 7 PHP preexistentes; novos scripts formatados',
'Formatação global pendente; checks dos componentes alterados passaram',
'Executável sem decisão; não reformatação massiva de alterações alheias',
],
[
'AUD-UI-01',
'P1',
'parcial',
'Inspeção visual de todas as rotas/ações e botões',
'governação/outros',
'Inventário de 387 rotas; guiões por módulo',
'Browser control e provas históricas',
'Perfil autorizado; criar/editar/guardar/recuperar/pesquisar/filtros/anexos/excluir/transições; screenshots e rede por percurso',
'Nesta sessão nenhum browser ligado; HTTP e testes não substituem UI',
'Percursos visuais novos não executados; não declarar ausência de botões inertes',
'Browser ligado e sessão QA; escrever somente na base QA',
],
];
// Source namespaces keep divergent requirements with the same original ID separate.
const idCounts = rows.reduce((counts, row) => {
counts[row.id] = (counts[row.id] || 0) + 1;
return counts;
}, {});
for (const row of rows) {
if (idCounts[row.id] < 2) continue;
const original = row.id;
const namespace = row.fontes.some((source) => source.includes('education/'))
? 'EDUCACAO'
: 'UPGRADE';
row.id = `${original}@${namespace}`;
row.variantes += `; ID original ${original}; namespace ${namespace}; textos não fundidos`;
}
for (const [
id,
priority,
status,
request,
module,
route,
code,
accept,
evidence,
gap,
dependency,
] of items) {
rows.push({
id,
pedido: request,
fontes: [
'Pedido direto 09/10/2026, auditoria §1–4; fontes específicas descritas em relatorio-executivo.md',
],
modulo: module,
rotas: route.split('; '),
implementacao: code,
ativo:
(tablesByDomain[module] || [])
.map(
(t) =>
`${t}: ${active.tables['public.' + t]?.rows ?? 'ausente'}`,
)
.join('; ') ||
'Requisito transversal; estado/configuração e snapshots descritos no relatório executivo',
evidencia: evidence,
estado: status,
lacuna: gap,
dependencia: dependency,
aceitacao: accept,
prioridade: priority,
variantes:
'Relatórios históricos mantidos; estado ativo apurado por leitura nesta auditoria',
});
}
// Follow-up evidence belongs to the same operational requirements, not a second matrix.
for (const [
id,
status,
pedido,
modulo,
rotas,
implementacao,
aceitacao,
lacuna,
] of [
[
'AUD-OP-05',
'com falha',
'Oito etapas de maturidade no cadastro de oportunidades',
'oportunidades',
[
'/management/opportunities/drafts',
'/management/opportunities/{id}/edit',
],
'OpportunityManagement::save between:1,6; opportunity-draft lista seis etapas',
'Catálogo de oito etapas da especificação com versão explícita; guardar/recuperar etapas 7 e 8; preservar interpretação das seis etapas antigas; filtro e ficha coerentes',
'POST das etapas 7 e 8 recebe 422; catálogo versionado de oito etapas ainda ausente. Não remapear automaticamente valores antigos.',
],
[
'AUD-EDU-02',
'não implementado',
'Rascunho incompleto privado de procura e capacidade',
'educação',
['/management/education/demands', '/management/education/capacities'],
'EducationController::store exige todos os campos e grava submitted; LabourDemand/TrainingCapacity existentes',
'Guardar incompleto, editar com conflito, recuperar por dono; só submissão exige campos completos; revisão por ator distinto; rascunho fora dos agregados públicos',
'Procura/capacidade incompletas recebem 422 sem gravação; não há percurso de rascunho incompleto. Rascunho de proposta de investimento e curso não satisfaz este pedido.',
],
[
'AUD-EDU-03',
'não implementado',
'Jornadas e painéis educativos por ator',
'educação',
['/educacao', '/management/education'],
'EducationController; schemas skills_edu; EducationInvestmentController',
'Percurso próprio e ações autorizadas por estudante, entidade formadora, empregador e gestor conforme fontes; guardar/recuperar e isolamento; não presumir mandato institucional',
'Gestão interna e catálogo não demonstram nem implementam jornadas/painéis completos por ator; reutilizar atores/contas e schemas existentes.',
],
[
'AUD-DOC-01',
'parcial',
'Reconciliar estados documentais antigos com evidências posteriores',
'governação/outros',
[
'docs/investmoz/v2/backlog-mestre.md',
'docs/investmoz/v2/gestao-oportunidades-dashboard.md',
],
'Matriz existente; relatórios históricos; snapshots ativos e testes atuais',
'Cada afirmação atual aponta para prova e data; versões antigas identificadas como históricas; divergência textual preservada; não apagar decisões anteriores',
'Contagens e novas correções reconciliadas nesta matriz; revisão semântica individual das cláusulas e demais estados documentais ainda pendente.',
],
[
'AUD-COUNTRY-06',
'parcial',
'País pesquisável do proponente no wizard de negócios existentes',
'negócios',
['/investidor/negocios'],
'business-wizard formSchema.country é texto; country persistido no payload existente',
'Catálogo ISO pesquisável; código válido guardado e recuperado no payload canónico; valor legado visível/conservado; ZZ 422 sem versão nova; outra conta negada',
'CountrySelect ainda não aplicado a este consumidor; validação/recuperação individual do novo ISO ainda pendente.',
],
[
'AUD-COUNTRY-07',
'parcial',
'País pesquisável no formulário de assistência fundiária',
'oportunidades',
['/investidor/terra-duat'],
'land-assistance/form text(country); LandAssistanceController usa payload.country',
'Catálogo ISO pesquisável opcional; guardar/recuperar país sem inventar nacionalidade/origem; legado exato; erro no campo e privacidade',
'Campo permanece texto livre; ligação ao catálogo comum e ensaio individual pendentes.',
],
])
rows.push({
id,
estado: status,
pedido,
modulo,
rotas,
implementacao,
aceitacao,
lacuna,
fontes: [
'Pedido direto 09/10/2026, principais lacunas e continuação; docs/investmoz/v2/gestao-oportunidades-dashboard.md §10; referências originais conservadas na matriz',
],
ativo: 'Consultar snapshot READ ONLY de 16:17:55 UTC; nenhum dado ativo alterado',
evidencia: ['AUD-OP-05', 'AUD-EDU-02'].includes(id)
? 'pending-actions.json: quatro pedidos 422 na cópia restaurada; hashes de oportunidades/procura/capacidade/histórico inalterados; browser pendente'
: 'Inspeção de código e documentação; demonstração individual completa pendente',
dependencia:
'Executável em QA sem ClamD nem nova aceitação de fontes; dados/designações concretos apenas para utilização institucional ativa',
prioridade: 'P1',
variantes:
'O relatório indicado em outputs/auditoria-portal-2026-10-09.md não foi localizado neste workspace; conteúdo da mensagem preservado como fonte direta, sem atribuir hash ao ficheiro não lido',
percurso: {
interface: 'Código inspecionado; browser pendente',
api: ['AUD-OP-05', 'AUD-EDU-02'].includes(id)
? 'QA reproduz 422; critério pedido continua a falhar'
: 'Não demonstrado individualmente',
gravacao: ['AUD-OP-05', 'AUD-EDU-02'].includes(id)
? 'Nenhuma gravação nas tentativas incompletas/etapas 7–8; hashes conservados'
: 'Não demonstrado individualmente',
recuperacao:
'Percurso solicitado ainda incompleto; não promover a concluído',
resultado: 'Falha/lacuna documentada; inspeção visual pendente',
},
});
const byId = Object.fromEntries(rows.map((r) => [r.id, r]));
const backupNow = JSON.parse(
fs.readFileSync(
path.join(priv, '../opportunity-resources-backup-proof.json'),
'utf8',
),
);
Object.assign(byId['AUD-OPS-01'], {
evidencia: `Backup/restauro ${backupNow.verified_at}; ${backupNow.restored_target.database}:55441; 288 tabelas e cinco ficheiros; compatibilidade/reversão QA do código atual`,
lacuna: 'Recursos ainda por instalar; prova renovada para o código atual. Browser atual pendente; não repetir Balcão/Bolsa/gestão básica.',
});
byId['AUD-OPS-02'].evidencia =
'Lock concorrente recusado antes de limpeza; suite sequencial atual: 373 passaram, 3 ClamD saltados, 4608 asserções';
byId['AUD-STYLE-01'].evidencia =
'check-followup.log: 16 ficheiros frontend/documentos/scripts; pint-followup.log: cinco PHP. Ficheiros alterados verificados seletivamente; falha global continua aberta';
const evidence =
'PortalAuditContinuationTest: 5 testes / 203 asserções; phpunit-followup-recovery-final.log; API/Inertia/persistência demonstradas, browser indisponível';
Object.assign(byId['AUD-COUNTRY-04'], {
pedido: 'País e origem separados nos formulários públicos e percursos legados',
evidencia:
evidence + '; PortalOriginCompatibilityTest; InvestmentPortfolioTest',
lacuna: 'CountrySelect e recuperação preparados; percurso visual pendente. Países nos cadastros de negócios/fundiário continuam separados neste backlog.',
aceitacao:
'249 códigos pesquisáveis; guardar country=PT e origem=ZA sem confundir; recuperar no gestor/jornada; ZZ 422 sem escrita; texto legado exato conservado; replay sem duplicar',
});
Object.assign(byId['AUD-STATS-01'], {
evidencia: evidence,
lacuna: 'Filtro e agregados por origem demonstrados via HTTP/Inertia; resultado visual ainda pendente; não contar investidores únicos',
});
Object.assign(byId['AUD-GEO-02'], {
evidencia:
evidence +
'; territory-catalogue-coverage.json: 1920 IDs públicos em 597 pedidos',
lacuna: 'Balcão hierárquico e recuperação de quatro níveis preparados/demonstrados via API e props. Simulador ligado ao catálogo povoado. Restantes consumidores e UI individual continuam pendentes.',
});
Object.assign(byId['AUD-SIM-01'], {
evidencia:
evidence + '; SimulationFormDraftTest; SimulationDraftContinuityTest',
lacuna: 'ISO PT/ZA e legado Portugal recuperados em rascunho privado; países do simulador deixaram de consultar apenas jurisdição Moçambique. Aceitação visual e outros campos continuam pendentes.',
});
const pipelines = {
'AUD-COUNTRY-04': [
'CountrySelect em PortalInvestidor, apoio (4 tipos), interesse em negócio e jornada; código verificado, visual pendente',
'POST API interest-requests / apoio / percurso legado; ISO inválido 422 e replay',
'interest_requests.country=PT; metadata.origem=ZA; legado exato conservado',
'GET management/interests/{id} e jornada com país/origem separados',
'Props Inertia e JSX de país/origem; browser pendente',
],
'AUD-STATS-01': [
'Filtro CRM preparado; visual pendente',
'GET management/crm/leads?origin=ZA e PT; controlo de acesso',
'Origem persistida no pedido; estatística não cria tabela paralela',
'Agregação recupera ZA=1 e PT=0; conjuntos interest/desk separados',
'originStatistics Inertia demonstrada; filtro e tabela visuais pendentes',
],
'AUD-GEO-02': [
'TerritoryFields substitui lista plana do Balcão; simulador usa API territorial; visual pendente',
'597 GET recuperam 1920 IDs da cópia ativa; QA sintetica verifica quatro níveis',
'PUT pedido guarda locality ID no payload cifrado existente; ID privado 422 preserva versão',
'GET recupera ID e territoryPaths com province/district/admin_post/locality; outra conta negada',
'Props de quatro níveis e componente preparado; consumidores restantes e browser pendentes',
],
'AUD-SIM-01': [
'simulation-form países usa countries, território usa territory-options; visual pendente',
'POST/PUT/GET simulation-drafts; países PT/ZA, ZZ 422; If-Match',
'Snapshots privados cifrados; texto Portugal preservado; nenhuma execução fiscal',
'GET proprietário recupera ISO/lista e legado; outra conta 404',
'JSON de recuperação demonstrado; formulário renderizado pendente',
],
};
for (const [id, values] of Object.entries(pipelines))
byId[id].percurso = Object.fromEntries(
['interface', 'api', 'gravacao', 'recuperacao', 'resultado'].map(
(key, i) => [key, values[i]],
),
);
for (const id of ['AUD-OPS-02', 'AUD-OPS-03'])
byId[id].percurso = {
interface:
'Não aplicável: requisito operacional, sem interface de utilizador',
api: 'Guarda/teste de identidade e READ ONLY demonstrados',
gravacao:
'Escrita exclusiva QA; zero escrita ativa por estas ferramentas',
recuperacao: 'Backup/restauro e hashes comparados',
resultado: byId[id].evidencia,
};
for (const [id, pedido, aceitacao, evidencia] of [
[
'AUD-COUNTRY-05',
'Catálogo HTTP ISO comum independente da publicação de jurisdições',
'API retorna 249 códigos únicos paginados; pesquisa PT/EN/código e sem acentos; parâmetros incompatíveis 422; zero seed em geo_locations/reference_catalog_versions',
evidence,
],
[
'AUD-GEO-04',
'Recuperação integral do catálogo administrativo público pela API hierárquica',
'Na cópia restaurada, comparar todos os IDs públicos por tipo/pai/página sem omissões nem duplicados: 11/148/437/1324; não inclui mapas/geometrias nem persistência de todos os consumidores',
'territory-catalogue-coverage.json: QA 55441; 597 pedidos; 1920/1920 IDs; escrita ativa=0',
],
])
rows.push({
id,
pedido,
fontes: [
'Pedido direto 09/10/2026, continuação §2–4; caso adicional encontrado na auditoria',
],
modulo: id.includes('COUNTRY') ? 'simuladores' : 'cartografia/relações',
rotas: [
id.includes('COUNTRY')
? '/api/v1/catalogs/countries'
: '/api/v1/investment/territory-options',
],
implementacao: id.includes('COUNTRY')
? 'CountryCatalogue; ReferenceCatalogController'
: 'InvestmentTerritoryController; geo_locations existentes',
ativo: id.includes('COUNTRY')
? 'Catálogo local 249, sem migration; consumidor novo ainda não demonstrado no web ativo'
: 'Cadastro ativo 1990; públicos administrativos 1920; geo_geometries=0',
evidencia,
estado: 'concluído e demonstrado',
lacuna: 'Critério restrito à API em QA; os requisitos de formulários/cartografia global continuam parciais',
dependencia:
'Não depende de ClamD; nenhum dado inventado ou importação ativa',
aceitacao,
prioridade: 'P1',
variantes: 'Caso novo; não substitui AUD-COUNTRY-01–04 / AUD-GEO-01–03',
percurso: {
interface:
'Não aplicável ao critério de catálogo HTTP; consumidores de UI mantidos parciais',
api: evidencia,
gravacao: 'Catálogo somente leitura; não altera dados',
recuperacao: aceitacao,
resultado: 'Resposta JSON comparada; não certifica UI ou mapas',
},
});
// The next lot updates these existing requirements; historical reproductions remain retained.
const nextEvidence =
'PortalAuditNextLotTest; phpunit-lote2-final.log/xml; API/persistência/Inertia demonstradas, UI visual pendente';
byId['AUD-OPS-02'].evidencia =
'Suite sequencial QA: 379/379 testes, 4922 asserções, zero skips; guarda de identidade/lock preservada';
byId['AUD-OPS-01'].lacuna =
'Recursos não instalados. Prova renovada para o fingerprint do lote atual; ready é preparação, não instalação/conclusão funcional. Não repetir Balcão/Bolsa/gestão básica.';
Object.assign(byId['AUD-OP-05'], {
estado: 'parcial',
implementacao:
'OpportunityMaturity: spec-v2 (oito códigos) e legacy-v1 (seis posições); OpportunityManagement; seletor com catálogo recebido do servidor',
evidencia:
nextEvidence +
'; oito POST/GET; 7/8 201; replay único; 409; divergência código/posição 422; legado 6 mantém rótulo/coluna',
lacuna: 'Rejeição 422 dos níveis 7/8 corrigida no draft. UI renderizada e adoção/revisão/publicação da nova versão continuam pendentes; filtros públicos continuam explicitamente na escala histórica, sem ler draft privado.',
dependencia:
'Sem migration adicional: etapa/posição/versão no management_draft existente. Workflow de publicação ainda pendente; nenhuma conversão automática dos seis valores antigos.',
percurso: {
interface: 'Oito etapas e opção histórica conservada; visual pendente',
api: 'POST draft 1–8 e PUT código/versão; inconsistência 422, conflito 409, outro perfil 403',
gravacao:
'management_draft guarda maturity_stage/level/catalogue_version; colunas históricas intactas; audit_events preservados',
recuperacao:
'GET edit recupera código, posição, rótulo e versão; catálogo server-side com oito entradas',
resultado:
'Props do formulário verificadas; publicação/filtros públicos novos ainda pendentes',
},
});
for (const id of ['AUD-COUNTRY-06', 'AUD-COUNTRY-07'])
Object.assign(byId[id], {
evidencia: nextEvidence,
estado: 'parcial',
implementacao: id.endsWith('06')
? 'BusinessFormSchema country/origin; CountrySelect no wizard; payload cifrado existente; filtros autorizados; agregado de negócios submetidos'
: 'CountrySelect fundiário; payload cifrado; origem mantida no CRM/receipt; nomes e caminho territorial recuperados na ficha',
lacuna: 'Percurso API/gravação/recuperação/filtros/estatísticas demonstrado individualmente. Visual atual pendente; não encerrar cobertura transversal por este formulário.',
dependencia:
'Sem migration ou ClamD; países existentes. Origem opcional explicitamente declarada, sem inferência a partir de residência/constituição.',
percurso: {
interface:
'CountrySelect para país e origem; props 249; visual pendente',
api: id.endsWith('06')
? 'POST/PUT/GET listings; submissão privada; country/origin filters; outra conta 403/lista vazia'
: 'POST/PUT/GET caso; solicitar-apoio; gestor filtra país/origem; outra conta 404/403',
gravacao:
'País PT/origem ZA separados no payload cifrado; ZZ 422 conserva versão; sem tabela paralela',
recuperacao:
'GET recupera ambos; countryName/declaredOrigin no consumidor; negócios excluem drafts dos agregados; fundiário integra interesses uma vez',
resultado:
'Props, filtros e agregados verificados; UI ainda sem demonstração visual',
},
});
Object.assign(byId['AUD-STATS-01'], {
evidencia:
nextEvidence +
'; origem fundiária no agregado interests; negócios submitted no agregado businesses; desk separado',
lacuna: 'Filtros por país/origem e estatísticas por conjunto demonstrados no servidor. UI pendente. Rascunhos excluídos; país não substitui origem; sem contagem de investidores únicos.',
percurso: {
interface: 'Filtros e contagens nos pedidos/CRM; visual pendente',
api: 'GET interests country=PT/origin=ZA, origem PT/unknown; GET negócios filtrado pela coleção autorizada',
gravacao:
'Origem no metadata interest/desk e payload cifrado business; sem backfill histórico',
recuperacao:
'ZA=1/PT=0 por percurso; business draft=0/submitted=1; fundiário não duplicado',
resultado:
'originStatistics e listas Inertia/API demonstradas; visual pendente',
},
});
Object.assign(byId['AUD-GEO-02'], {
evidencia:
nextEvidence +
'; C17: província com pai país guardada/recuperada no fundiário, quatro níveis e nomes na ficha',
lacuna: 'Falha fundiária de hierarquia corrigida; Balcão e fundiário demonstrados no servidor. Demais seletores/mapas/perfis e inspeção visual continuam individualmente pendentes.',
});
Object.assign(byId['AUD-GEO-03'], {
evidencia:
'geometry-qa-adm1/2/3-preview/import.json: 361 geometrias privadas QA (9/120/232), ST_IsValid/SRID4326 e recuperação; segundo lote idempotente; metadados INE/UNOCHA 2019-06-07; hashes de originais retidos',
lacuna: 'Base ativa continua com zero geometrias. Origem oficial identificada aceite; edição 2019 não equivale a vigência atual. 2/39/179 elementos da fonte sem correspondência inequívoca; localidades sem polígonos nesta fonte. Mapas públicos ainda não recebem o lote privado nem pontos de investimento.',
dependencia:
'Reconciliar nomes/pais por identidade administrativa, sem fuzzy match; revisão/autorização autenticada para eventual uso ativo; nunca publicar lote automaticamente. Adm3 excede limite de upload 20MB: scan GIS CLI específico, sem aumentar limites do portal.',
percurso: {
interface:
'Consumidores/mapas inventariados por código; visual pendente',
api: 'Catálogo 1920 demonstrado apenas como leitura; import privado não popula API de mapas',
gravacao:
'361 geo_geometries existentes na QA, classificação internal, proveniência/hash/versão; zero escrita ativa',
recuperacao:
'ST_AsGeoJSON/PostGIS recupera geometria válida; reaplicação reutiliza os mesmos IDs; relações/territórios não alterados',
resultado:
'Mapas globais continuam parciais; correspondências/lacunas listadas por entidade, sem pontos fictícios',
},
});
Object.assign(byId['AUD-SCAN-01'], {
estado: 'parcial',
evidencia:
'clamd-lote2.json; clamd-lote2-tests.log: seis testes reais/29 asserções; ClamAV 1.5.4 em loopback, assinaturas 2026-10-09T08:25:40Z, EICAR detetado em memória; worker QA guarda clean/hash sem publicar',
lacuna: 'Falha de scanner indisponível resolvida na sessão. Arranque após reboot/atualização periódica e GD/WebP no PHP web ativo por configurar/verificar; scan próprio das derivadas reais ainda necessário por ficheiro.',
dependencia:
'Manter daemon/assinaturas; afeta somente scan/libertação de binários e derivadas. Não bloqueia países, origem, estatísticas, rascunhos ou catálogo territorial.',
percurso: {
interface: 'Estados de scan/derivadas preparados; UI pendente',
api: 'PING/VERSION/INSTREAM reais; EICAR FOUND; indisponibilidade/limites continuam inconclusive',
gravacao:
'Fila database QA passa pending -> clean, hash registado; ficheiro permanece internal',
recuperacao:
'documents/document_malware_scans recuperados pelo teste do worker; nenhuma publicação',
resultado:
'Scanner real saudável nesta sessão; não certifica manutenção após reboot ou web ativo',
},
});
Object.assign(byId['AUD-STYLE-01'], {
estado: 'concluído e demonstrado',
evidencia:
'check-lote2-final.log: formato global passa, lint 0 erros/11 avisos; pint-lote2-final.log passed; diff preservado',
lacuna: 'Critério de formatação encerrado; avisos lint preexistentes não foram omitidos e não equivalem a falha de persistência.',
dependencia: 'Sem decisão/instalação; mesmos formatadores existentes',
percurso: {
interface: 'Não aplicável a checks de formato',
api: 'Não aplicável',
gravacao:
'Só ficheiros de código/documentação; dados ativos preservados',
recuperacao: 'Checks relidos e resultados registados',
resultado: 'Formatadores globais passam; nenhum erro lint',
},
});
Object.assign(byId['AUD-UI-01'], {
evidencia:
'consumer-actions-lote2.json: inventário AST de botões/seletores/mapas; C20 corrige ícone Pesquisar sem ação no cabeçalho; rota existente /oportunidades demonstrada em QA; browser iab indisponível',
lacuna: 'Percursos visuais completos por ator/consumidor continuam pendentes. Ligações estáticas, Form Inertia e triggers delegados não certificam resultado funcional; não declarar todos os botões aceites.',
});
// Lote 3 updates the same requirements, retaining original clauses and divergent versions.
const consumerEvidence =
'PortalAuditConsumerLotTest: 2 testes/124 asserções; phpunit-lote3-final.xml; geometry-consumers-lote3.json: 361 limites reais recuperados em QA, zero divulgação pública; visual não demonstrado';
for (const id of [
'AUD-COUNTRY-04',
'AUD-GEO-02',
'AUD-GEO-03',
'AUD-UI-01',
'AUD-DESK-01',
'AUD-DOC-01',
'AUD-OPS-01',
]) {
byId[id].fontes.push(
'Pedido direto 09/10/2026, lote 3: ações por perfil, países/seletores, geometria por consumidor, navegador, ativação separada e PDFs diretos; sem jornadas educativas novas',
);
}
byId['AUD-COUNTRY-04'].evidencia +=
'; Balcão nos três serviços: país PT/origem ZA cifrados, GET recupera, ZZ 422; devolução permite só campos autorizados, versão5/conflito409/perfil403';
byId['AUD-COUNTRY-04'].percurso.gravacao +=
'; country e origem do Balcão cifrados sem alterar as definições históricas de serviço';
byId['AUD-COUNTRY-04'].percurso.recuperacao +=
'; três formulários Balcão recuperam country/origin, catálogo249';
byId['AUD-DESK-01'].evidencia = consumerEvidence;
byId['AUD-DESK-01'].lacuna =
'C21: país do requerente e devolução de país/origem corrigidos nos três serviços em QA. UI atual e restantes ações/anexos por perfil continuam pendentes; não afirmar o percurso inteiro do Balcão concluído.';
byId['AUD-GEO-02'].evidencia +=
'; context preview ligado a TerritoryFields, gestão territorial, draft/recursos de oportunidades e mapas do catálogo/painel; source frontend preparado, visual pendente';
byId['AUD-GEO-02'].percurso.resultado =
'Campos/mapas consumidores preparados com estado cadastrado, geometria da seleção e contexto dos pais separados; browser sem provider, não observado';
byId['AUD-GEO-03'].evidencia = consumerEvidence;
byId['AUD-GEO-03'].implementacao +=
'; TerritoryGeometry + endpoint público/gestor existente; TerritoryGeometryPreview reutiliza Leaflet com legenda/edição';
byId['AUD-GEO-03'].lacuna =
'Contexto administrativo integrado no código dos consumidores; recuperação361/361 real em QA e autorização HTTP sintética demonstradas. Não publicado, visual pendente. Lacunas2/39/179 e nenhuma geometria de localidade; limites administrativos não são locais/perímetros de investimento.';
byId['AUD-GEO-03'].percurso = {
interface:
'Preview no seletor partilhado, gestão territorial, oportunidades draft/recursos e catálogo/painel de mapas; renderização não observada',
api: 'GET territory-geometries público/gestor, UUID inválido422, gestor autorizado/conta comum403, geometrias internas não expostas publicamente',
gravacao:
'Reutiliza361 geometrias QA internas previamente importadas; nova consulta não escreve nem publica',
recuperacao:
'361 limites oficiais reais recuperados por território na QA171209; localidade sem polígono próprio mostra só contexto ancestral, sem atribuição fictícia',
resultado:
'GeoJSON simplificado, edição2019/hash/origem e contagens independentes; legenda/estados preparados, visual pendente',
};
byId['AUD-UI-01'].evidencia =
'phpunit-lote3-final.xml, ações CRM/Bolsa/recursos/fundiário/correções Balcão por perfil; resultado GET/Inertia/eventos no servidor; consumer-actions-lote2.json atualizado:363 botões/272 seletores/mapas; browser getState vazio, chrome/edge indisponíveis mesmo após reset';
byId['AUD-UI-01'].lacuna =
'Resultado HTML/props/flash/API/persistência separado do resultado visual. QA HTTP recuperada em18096, provider de UI ausente; ligação de navegador solicitada. Não certificar todos os363 botões por suite/global/inventário.';
byId['AUD-DOC-01'].lacuna +=
' Pedido anterior de PDFs preservado como histórico; pedido atual dispensa PDFs para continuar e aceita resumo/ativação na conversa. Revisão documental anterior não presumida pelos links Adobe. Sem dependência atual de gerar/anexar PDF.';
const currentTests = JSON.parse(
fs.readFileSync(path.join(priv, 'phpunit-final.json'), 'utf8'),
);
byId['AUD-OPS-02'].evidencia =
`Suite sequencial QA: ${currentTests.passed}/${currentTests.tests} testes, ${currentTests.assertions} asserções; skips${currentTests.skipped ?? 0}; guarda/lock preservados; ${currentTests.junit ?? 'phpunit-lote3-final.xml'}`;
require('./portal-investor-consolidation-review.cjs')(rows, inventory);
// Preserve the complete source register before replacing the public matrix with canonical rows.
fs.writeFileSync(
path.join(priv, 'matriz-linhas-origem.json'),
JSON.stringify(rows, null, 2),
);
const consolidated = require('./portal-audit-consolidate.cjs')(rows);
rows = consolidated.rows;
fs.writeFileSync(
path.join(priv, 'contagens-requisitos.json'),
JSON.stringify(consolidated.summary, null, 2),
);
const preserved = Object.entries(before.tables).map(([t, v]) => ({
table: t,
before: v.sha256,
after: active.tables[t]?.sha256,
unchanged: v.sha256 === active.tables[t]?.sha256,
}));
if (new Set(rows.map((row) => row.id)).size !== rows.length) {
throw new Error(
'Requirement IDs must be unique, including source variants',
);
}
fs.writeFileSync(
path.join(priv, 'preservacao.json'),
JSON.stringify(
{
active_writes: 0,
tables_compared: preserved.length,
changed: preserved.filter((r) => !r.unchanged),
checks: preserved,
},
null,
2,
),
);
fs.writeFileSync(
path.join(priv, 'matriz-revista.json'),
JSON.stringify(rows, null, 2),
);
const headers = [
'ID',
'Pedido/trecho',
'Fonte/ocorrências',
'Módulo',
'Rotas',
'Implementação encontrada',
'Disponibilidade ativa',
'Evidência funcional',
'Estado',
'Lacuna',
'Dependência',
'Critério de aceitação',
'Prioridade',
'Versões divergentes',
'IDs das linhas de origem',
'Quantidade de linhas de origem',
'Critérios e versões por origem (JSON)',
'Interface',
'API',
'Gravação',
'Recuperação',
'Resultado apresentado',
'Consolidação',
];
fs.writeFileSync(
path.join(out, 'matriz-requisitos.csv'),
'\uFEFF' +
[
headers,
...rows.map((r) => [
r.id,
r.pedido,
r.fontes.join(' | '),
r.modulo,
r.rotas.join('; '),
r.implementacao,
r.ativo,
r.evidencia,
r.estado,
r.lacuna,
r.dependencia,
r.aceitacao,
r.prioridade,
r.variantes,
r.aliases.join(' | '),
r.linhas_origem,
JSON.stringify(r.origens),
...[
'interface',
'api',
'gravacao',
'recuperacao',
'resultado',
].map((key) => r.percurso[key]),
r.consolidacao,
]),
]
.map((row) => row.map(csvCell).join(','))
.join('\n') +
'\n',
);
fs.writeFileSync(
path.join(out, 'backlog-priorizado.csv'),
'\uFEFF' +
[
[
'Prioridade',
'ID',
'Estado',
'Pendência',
'Correção/componentes',
'Dependência/pergunta',
'Aceitação',
'Ordem do próximo lote',
],
...[...rows]
.sort((a, b) => a.prioridade.localeCompare(b.prioridade))
.map((r) => [
r.prioridade,
r.id,
r.estado,
r.lacuna,
r.implementacao,
r.dependencia,
r.aceitacao,
/AUD-(UI|DASH|DESK|CRM|BIZ|LAND)-/.test(r.id)
? 1
: /AUD-(COUNTRY|STATS|SIM)-/.test(r.id)
? 2
: /AUD-GEO-0[12]|AUD-FIELD-/.test(r.id)
? 3
: r.id === 'AUD-GEO-03'
? 4
: /AUD-OP-0[345]/.test(r.id)
? 5
: /AUD-EDU-/.test(r.id)
? 6
: 'Critério individual; consultar backlog',
]),
]
.map((row) => row.map(csvCell).join(','))
.join('\n') +
'\n',
);
fs.writeFileSync(
path.join(out, 'cobertura-rotas.csv'),
'\uFEFF' +
[
['Perfil', 'Rota', 'HTTP', 'Cobertura'],
...routes.results.map((r) => [
r.profile,
r.route,
r.status,
r.coverage,
]),
]
.map((row) => row.map(csvCell).join(','))
.join('\n') +
'\n',
);
const fileRows = [];
for (const file of inventory.files) {
fileRows.push([
file.path,
file.bytes,
file.sha256,
'original',
file.read_error || '',
file.consultation,
file.rule_use,
file.document_status ||
'Situação documental não determinada por inventário',
file.source_version || '',
]);
for (const attachment of file.attachments) {
fileRows.push([
file.path + '#' + attachment.path,
attachment.bytes,
attachment.sha256,
'anexo embebido',
'',
file.consultation,
file.rule_use,
file.document_status ||
'Situação documental não determinada por inventário',
file.source_version || '',
]);
}
}
fs.writeFileSync(
path.join(out, 'inventario-fontes.csv'),
'\uFEFF' +
[
[
'Exemplar/caminho',
'Bytes',
'SHA256',
'Tipo',
'Nota de leitura',
'Disponibilidade',
'Uso em cálculo',
'Situação jurídica/documental',
'Edição/versão identificada',
],
...fileRows,
]
.map((row) => row.map(csvCell).join(','))
.join('\n') +
'\n',
);
const testRows = [];
function inventoryTests(dir) {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const file = path.join(dir, entry.name);
if (entry.isDirectory()) inventoryTests(file);
else if (entry.name.endsWith('Test.php')) {
const content = fs.readFileSync(file, 'utf8');
for (const match of content.matchAll(
/public function (test_[a-zA-Z0-9_]+)\(/g,
)) {
testRows.push([
path.relative(root, file).replace(/\\/g, '/'),
match[1],
'Inventário estático; resultado global em phpunit-final.json; não certifica cláusula/UI individual',
]);
}
}
}
}
inventoryTests(path.join(root, 'tests'));
fs.writeFileSync(
path.join(out, 'cobertura-testes.csv'),
'\uFEFF' +
[['Ficheiro', 'Método', 'Limite da evidência'], ...testRows]
.map((row) => row.map(csvCell).join(','))
.join('\n') +
'\n',
);
console.log(
JSON.stringify({
requirements: rows.length,
source_lines: consolidated.summary.linhas_origem,
counts: consolidated.summary.por_estado,
reviewed_items: items.length,
preservation_changed: preserved
.filter((r) => !r.unchanged)
.map((r) => r.table),
route_checks: routes.results.length,
statuses: rows.reduce(
(a, r) => ((a[r.estado] = (a[r.estado] || 0) + 1), a),
{},
),
}),
);