1397 lines
66 KiB
JavaScript
1397 lines
66 KiB
JavaScript
// 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),
|
||
{},
|
||
),
|
||
}),
|
||
);
|